Skip to content

Commit 0b77933

Browse files
author
synw
committed
Initial
1 parent 56bec37 commit 0b77933

File tree

4 files changed

+204
-2
lines changed

4 files changed

+204
-2
lines changed

README.md

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,37 @@
1-
# quidjs
2-
A requests library for the Quid json web tokens server
1+
# Quidjs
2+
3+
A requests library for the [Quid](https://github.com/synw/quid) json web tokens server
4+
5+
This library transparently manage the requests to api servers. If a server returns a 401 Unauthorized response
6+
when an access token is expired the client library will request a new access token from a Quid server, using a refresh
7+
token, and will retry the request with the new access token
8+
9+
## Usage
10+
11+
```javascript
12+
var requests = new QuidRequests({
13+
namespace: "my_namespace",
14+
timeouts: {
15+
accessToken: "5m",
16+
refreshToken: "24h"
17+
},
18+
axiosConfig: {
19+
baseURL: "https://myquideserver.com",
20+
timeout: 5000
21+
},
22+
})
23+
24+
async function get(uri) {
25+
try {
26+
let response = await requests.get(uri);
27+
return { response: response, error: null }
28+
} catch (e) {
29+
if (e.hasToLogin) {
30+
// the user has no refresh token: a login is required
31+
}
32+
return { response: null, error: e }
33+
}
34+
}
35+
```
36+
37+

package.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "quidjs",
3+
"version": "0.1.0",
4+
"description": "Requests library for the Quid json web tokens server",
5+
"main": "requests.js",
6+
"keywords": [
7+
"quid",
8+
"jwt",
9+
"requests",
10+
"auth"
11+
],
12+
"author": "synw",
13+
"license": "MIT",
14+
"homepage": "https://github.com/synw/quidjs",
15+
"dependencies": {
16+
"axios": "^0.19.2"
17+
}
18+
}

src/exceptions.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default function quidException({ error, hasToLogin = false, unauthorized = false }) {
2+
return {
3+
hasToLogin: hasToLogin,
4+
error: error,
5+
unauthorized: unauthorized
6+
}
7+
}

src/requests.js

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import axios from 'axios'
2+
import quidException from "./exceptions"
3+
4+
export default class QuidRequests {
5+
refreshToken = null;
6+
#accessToken = null;
7+
8+
constructor({ namespace, axiosConfig, timeouts = {
9+
accessToken: "20m",
10+
refreshToken: "24h"
11+
},
12+
accessTokenUri = null,
13+
verbose = false }) {
14+
if (typeof namespace !== 'string') {
15+
throw quidException({ error: 'Parameter namespace has to be set' });
16+
}
17+
if (typeof axiosConfig !== 'object') {
18+
throw quidException({ error: 'Parameter axiosConfig has to be set' });
19+
}
20+
this.namespace = namespace
21+
this.axiosConfig = axiosConfig;
22+
this.axios = axios.create(this.axiosConfig);
23+
this.timeouts = timeouts
24+
this.verbose = verbose
25+
this.accessTokenUri = accessTokenUri
26+
}
27+
28+
async get(uri) {
29+
return await this._requestWithRetry(uri, "get")
30+
}
31+
32+
async post(uri, payload) {
33+
return await this._requestWithRetry(uri, "post", payload)
34+
}
35+
36+
async adminLogin(username, password) {
37+
let uri = "/admin_login";
38+
let payload = {
39+
namespace: "quid",
40+
username: username,
41+
password: password,
42+
}
43+
try {
44+
let response = await axios.post(uri, payload, this.axiosConfig);
45+
this.refreshToken = response.data.token;
46+
} catch (e) {
47+
if (e.response) {
48+
if (e.response.status === 401) {
49+
throw quidException({ error: null, unauthorized: true });
50+
}
51+
}
52+
throw quidException({ error: e });
53+
}
54+
}
55+
56+
async _requestWithRetry(uri, method, payload, retry = 0) {
57+
if (this.verbose) {
58+
console.log(method + " request to " + uri)
59+
}
60+
await this.checkTokens();
61+
try {
62+
if (method === "get") {
63+
return await this.axios.get(uri, this.axiosConfig);
64+
} else {
65+
return await axios.post(uri, payload, this.axiosConfig);
66+
}
67+
} catch (e) {
68+
if (e.response) {
69+
if (e.response.status === 401) {
70+
if (this.verbose) {
71+
console.log("Access token has expired")
72+
}
73+
this.#accessToken = null;
74+
await this.checkTokens();
75+
if (retry > 2) {
76+
throw quidException({ error: "too many retries" });
77+
}
78+
retry++
79+
if (this.verbose) {
80+
console.log("Retrying", method, "request to", uri, ", retry", retry)
81+
}
82+
return await this._requestWithRetry(uri, method, payload, retry)
83+
} else {
84+
throw quidException({ error: e });
85+
}
86+
} else {
87+
throw quidException({ error: e });
88+
}
89+
}
90+
}
91+
92+
async checkTokens() {
93+
if (this.refreshToken === null) {
94+
if (this.verbose) {
95+
console.log("Tokens check: no refresh token")
96+
}
97+
throw quidException({ error: 'No refresh token found', hasToLogin: true });
98+
}
99+
if (this.#accessToken === null) {
100+
if (this.verbose) {
101+
console.log("Tokens check: no access token")
102+
}
103+
let { token, error, statusCode } = await this._getAccessToken();
104+
if (error !== null) {
105+
if (statusCode === 401) {
106+
if (this.verbose) {
107+
console.log("The refresh token has expired")
108+
}
109+
throw quidException({ error: 'The refresh token has expired', hasToLogin: true });
110+
} else {
111+
throw quidException({ error: error });
112+
}
113+
}
114+
this.#accessToken = token;
115+
this.axiosConfig.headers.Authorization = "Bearer " + this.#accessToken
116+
this.axios = axios.create(this.axiosConfig);
117+
}
118+
}
119+
120+
async _getAccessToken() {
121+
try {
122+
let payload = {
123+
namespace: this.namespace,
124+
refresh_token: this.refreshToken,
125+
}
126+
let url = "/token/access/" + this.timeouts.accessToken
127+
if (this.accessTokenUri !== null) {
128+
url = this.accessTokenUri
129+
}
130+
if (this.verbose) {
131+
console.log("Getting an access token from", url, payload)
132+
}
133+
let response = await axios.post(url, payload, this.axiosConfig);
134+
return { token: response.data.token, error: null, statusCode: response.status };
135+
} catch (e) {
136+
if (e.response !== undefined) {
137+
return { token: null, error: e.response.data.error, statusCode: e.response.status };
138+
}
139+
return { token: null, error: e, statusCode: null }
140+
}
141+
}
142+
}

0 commit comments

Comments
 (0)