Skip to content

Commit

Permalink
Initial
Browse files Browse the repository at this point in the history
  • Loading branch information
synw committed Aug 29, 2020
1 parent 56bec37 commit 0b77933
Show file tree
Hide file tree
Showing 4 changed files with 204 additions and 2 deletions.
39 changes: 37 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,37 @@
# quidjs
A requests library for the Quid json web tokens server
# Quidjs

A requests library for the [Quid](https://github.com/synw/quid) json web tokens server

This library transparently manage the requests to api servers. If a server returns a 401 Unauthorized response
when an access token is expired the client library will request a new access token from a Quid server, using a refresh
token, and will retry the request with the new access token

## Usage

```javascript
var requests = new QuidRequests({
namespace: "my_namespace",
timeouts: {
accessToken: "5m",
refreshToken: "24h"
},
axiosConfig: {
baseURL: "https://myquideserver.com",
timeout: 5000
},
})

async function get(uri) {
try {
let response = await requests.get(uri);
return { response: response, error: null }
} catch (e) {
if (e.hasToLogin) {
// the user has no refresh token: a login is required
}
return { response: null, error: e }
}
}
```


18 changes: 18 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "quidjs",
"version": "0.1.0",
"description": "Requests library for the Quid json web tokens server",
"main": "requests.js",
"keywords": [
"quid",
"jwt",
"requests",
"auth"
],
"author": "synw",
"license": "MIT",
"homepage": "https://github.com/synw/quidjs",
"dependencies": {
"axios": "^0.19.2"
}
}
7 changes: 7 additions & 0 deletions src/exceptions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function quidException({ error, hasToLogin = false, unauthorized = false }) {
return {
hasToLogin: hasToLogin,
error: error,
unauthorized: unauthorized
}
}
142 changes: 142 additions & 0 deletions src/requests.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import axios from 'axios'
import quidException from "./exceptions"

export default class QuidRequests {
refreshToken = null;
#accessToken = null;

constructor({ namespace, axiosConfig, timeouts = {
accessToken: "20m",
refreshToken: "24h"
},
accessTokenUri = null,
verbose = false }) {
if (typeof namespace !== 'string') {
throw quidException({ error: 'Parameter namespace has to be set' });
}
if (typeof axiosConfig !== 'object') {
throw quidException({ error: 'Parameter axiosConfig has to be set' });
}
this.namespace = namespace
this.axiosConfig = axiosConfig;
this.axios = axios.create(this.axiosConfig);
this.timeouts = timeouts
this.verbose = verbose
this.accessTokenUri = accessTokenUri
}

async get(uri) {
return await this._requestWithRetry(uri, "get")
}

async post(uri, payload) {
return await this._requestWithRetry(uri, "post", payload)
}

async adminLogin(username, password) {
let uri = "/admin_login";
let payload = {
namespace: "quid",
username: username,
password: password,
}
try {
let response = await axios.post(uri, payload, this.axiosConfig);
this.refreshToken = response.data.token;
} catch (e) {
if (e.response) {
if (e.response.status === 401) {
throw quidException({ error: null, unauthorized: true });
}
}
throw quidException({ error: e });
}
}

async _requestWithRetry(uri, method, payload, retry = 0) {
if (this.verbose) {
console.log(method + " request to " + uri)
}
await this.checkTokens();
try {
if (method === "get") {
return await this.axios.get(uri, this.axiosConfig);
} else {
return await axios.post(uri, payload, this.axiosConfig);
}
} catch (e) {
if (e.response) {
if (e.response.status === 401) {
if (this.verbose) {
console.log("Access token has expired")
}
this.#accessToken = null;
await this.checkTokens();
if (retry > 2) {
throw quidException({ error: "too many retries" });
}
retry++
if (this.verbose) {
console.log("Retrying", method, "request to", uri, ", retry", retry)
}
return await this._requestWithRetry(uri, method, payload, retry)
} else {
throw quidException({ error: e });
}
} else {
throw quidException({ error: e });
}
}
}

async checkTokens() {
if (this.refreshToken === null) {
if (this.verbose) {
console.log("Tokens check: no refresh token")
}
throw quidException({ error: 'No refresh token found', hasToLogin: true });
}
if (this.#accessToken === null) {
if (this.verbose) {
console.log("Tokens check: no access token")
}
let { token, error, statusCode } = await this._getAccessToken();
if (error !== null) {
if (statusCode === 401) {
if (this.verbose) {
console.log("The refresh token has expired")
}
throw quidException({ error: 'The refresh token has expired', hasToLogin: true });
} else {
throw quidException({ error: error });
}
}
this.#accessToken = token;
this.axiosConfig.headers.Authorization = "Bearer " + this.#accessToken
this.axios = axios.create(this.axiosConfig);
}
}

async _getAccessToken() {
try {
let payload = {
namespace: this.namespace,
refresh_token: this.refreshToken,
}
let url = "/token/access/" + this.timeouts.accessToken
if (this.accessTokenUri !== null) {
url = this.accessTokenUri
}
if (this.verbose) {
console.log("Getting an access token from", url, payload)
}
let response = await axios.post(url, payload, this.axiosConfig);
return { token: response.data.token, error: null, statusCode: response.status };
} catch (e) {
if (e.response !== undefined) {
return { token: null, error: e.response.data.error, statusCode: e.response.status };
}
return { token: null, error: e, statusCode: null }
}
}
}

0 comments on commit 0b77933

Please sign in to comment.