-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
synw
committed
Aug 29, 2020
1 parent
56bec37
commit 0b77933
Showing
4 changed files
with
204 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } | ||
} | ||
} | ||
``` | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } | ||
} | ||
} | ||
} |