Skip to content

Commit 4e1d8a8

Browse files
created db & model factories
1 parent 1a387f4 commit 4e1d8a8

File tree

5 files changed

+212
-82
lines changed

5 files changed

+212
-82
lines changed

test/integration/flows/password-grant.js renamed to test/compliance/password-grant-type_test.js

Lines changed: 94 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,80 @@
1-
const OAuth2Server = require('../../../');
2-
const db = require('./db');
3-
const model = require('./model');
4-
const Request = require('../../../lib/request');
5-
const Response = require('../../../lib/response');
1+
/**
2+
* Request
3+
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-4.3.2
4+
*
5+
* grant_type
6+
* REQUIRED. Value MUST be set to "password".
7+
* username
8+
* REQUIRED. The resource owner username.
9+
* password
10+
* REQUIRED. The resource owner password.
11+
* scope
12+
* OPTIONAL. The scope of the access request as described by Section 3.3.
13+
*/
14+
15+
/**
16+
* Response
17+
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-5.1
18+
*
19+
* access_token
20+
* REQUIRED. The access token issued by the authorization server.
21+
* token_type
22+
* REQUIRED. The type of the token issued as described in
23+
* Section 7.1. Value is case insensitive.
24+
* expires_in
25+
* RECOMMENDED. The lifetime in seconds of the access token. For
26+
* example, the value "3600" denotes that the access token will
27+
* expire in one hour from the time the response was generated.
28+
* If omitted, the authorization server SHOULD provide the
29+
* expiration time via other means or document the default value.
30+
* refresh_token
31+
* OPTIONAL. The refresh token, which can be used to obtain new
32+
* access tokens using the same authorization grant as described
33+
* in Section 6.
34+
* scope
35+
* OPTIONAL, if identical to the scope requested by the client;
36+
* otherwise, REQUIRED. The scope of the access token as
37+
* described by Section 3.3.
38+
*/
39+
40+
/**
41+
* Response (error)
42+
* @see https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
43+
*
44+
* error
45+
* REQUIRED. A single ASCII [USASCII] error code from the following:
46+
* invalid_request, invalid_client, invalid_grant
47+
* unauthorized_client, unsupported_grant_type, invalid_scope
48+
* error_description
49+
* OPTIONAL. Human-readable ASCII [USASCII] text providing
50+
* additional information, used to assist the client developer in
51+
* understanding the error that occurred.
52+
* error_uri
53+
* OPTIONAL. A URI identifying a human-readable web page with
54+
* information about the error, used to provide the client
55+
* developer with additional information about the error.
56+
*/
57+
58+
const OAuth2Server = require('../..');
59+
const DB = require('../helpers/db');
60+
const createModel = require('../helpers/model');
61+
const createRequest = require('../helpers/request');
62+
const Response = require('../../lib/response');
663

764
require('chai').should();
865

66+
const db = new DB();
67+
968
const auth = new OAuth2Server({
10-
model: model
69+
model: createModel(db)
1170
});
1271

1372
const user = db.saveUser({ id: 1, username: 'test', password: 'test'});
1473
const client = db.saveClient({ id: 'a', secret: 'b', grants: ['password'] });
1574
const scope = 'read write';
1675

1776
function createDefaultRequest () {
18-
const request = new Request({
77+
return createRequest({
1978
body: {
2079
grant_type: 'password',
2180
client_id: client.id,
@@ -28,20 +87,12 @@ function createDefaultRequest () {
2887
'content-type': 'application/x-www-form-urlencoded'
2988
},
3089
method: 'POST',
31-
query: {}
3290
});
33-
34-
request.is = function (header) {
35-
return this.headers['content-type'] === header;
36-
};
37-
38-
return request;
3991
}
4092

41-
describe('PasswordGrantType Integration Flow', function () {
93+
describe('PasswordGrantType Compliance', function () {
4294
describe('Authenticate', function () {
43-
44-
it ('Succesfull authentication', async function () {
95+
it ('Succesfull authorization', async function () {
4596
const request = createDefaultRequest();
4697
const response = new Response({});
4798

@@ -62,6 +113,32 @@ describe('PasswordGrantType Integration Flow', function () {
62113
db.refreshTokens.has(token.refreshToken).should.equal(true);
63114
});
64115

116+
it ('Succesfull authorization and authentication', async function () {
117+
const tokenRequest = createDefaultRequest();
118+
const tokenResponse = new Response({});
119+
120+
const token = await auth.token(tokenRequest, tokenResponse, {});
121+
122+
const authenticationRequest = createRequest({
123+
body: {},
124+
headers: {
125+
'Authorization': `Bearer ${token.accessToken}`
126+
},
127+
method: 'GET',
128+
query: {}
129+
});
130+
const authenticationResponse = new Response({});
131+
132+
const authenticated = await auth.authenticate(
133+
authenticationRequest,
134+
authenticationResponse,
135+
{});
136+
137+
authenticated.scope.should.equal(scope);
138+
authenticated.user.should.be.an('object');
139+
authenticated.client.should.be.an('object');
140+
});
141+
65142
it ('Username missing', async function () {
66143
const request = createDefaultRequest();
67144
const response = new Response({});

test/integration/flows/db.js renamed to test/helpers/db.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
class DB {
2-
32
constructor () {
43
this.users = new Map();
54
this.clients = [];
@@ -51,15 +50,21 @@ class DB {
5150
return this.accessTokens.get(accessToken);
5251
}
5352

53+
deleteAccessToken (accessToken) {
54+
this.accessTokens.delete(accessToken);
55+
}
56+
5457
saveRefreshToken (refreshToken, meta) {
5558
this.refreshTokens.set(refreshToken, meta);
5659
}
5760

5861
findRefreshToken (refreshToken) {
5962
return this.refreshTokens.get(refreshToken);
6063
}
61-
}
6264

63-
const db = new DB();
65+
deleteRefreshToken (refreshToken) {
66+
this.refreshTokens.delete(refreshToken);
67+
}
68+
}
6469

65-
module.exports = db;
70+
module.exports = DB;

test/helpers/model.js

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
const scopes = ['read', 'write'];
2+
3+
function createModel (db) {
4+
async function getUser (username, password) {
5+
return db.findUser(username, password);
6+
}
7+
8+
async function getClient (clientId, clientSecret) {
9+
return db.findClient(clientId, clientSecret);
10+
}
11+
12+
async function saveToken (token, client, user) {
13+
const meta = {
14+
clientId: client.id,
15+
userId: user.id,
16+
scope: token.scope,
17+
accessTokenExpiresAt: token.accessTokenExpiresAt,
18+
refreshTokenExpiresAt: token.refreshTokenExpiresAt
19+
};
20+
21+
token.client = client;
22+
token.user = user;
23+
24+
if (token.accessToken) {
25+
db.saveAccessToken(token.accessToken, meta);
26+
}
27+
28+
if (token.refreshToken) {
29+
db.saveRefreshToken(token.refreshToken, meta);
30+
}
31+
32+
return token;
33+
}
34+
35+
async function getAccessToken (accessToken) {
36+
const meta = db.findAccessToken(accessToken);
37+
38+
if (!meta) {
39+
return false;
40+
}
41+
42+
return {
43+
accessToken,
44+
accessTokenExpiresAt: meta.accessTokenExpiresAt,
45+
user: db.findUserById(meta.userId),
46+
client: db.findClientById(meta.clientId),
47+
scope: meta.scope
48+
};
49+
}
50+
51+
async function getRefreshToken (refreshToken) {
52+
const meta = db.findRefreshToken(refreshToken);
53+
54+
if (!meta) {
55+
return false;
56+
}
57+
58+
return {
59+
refreshToken,
60+
refreshTokenExpiresAt: meta.refreshTokenExpiresAt,
61+
user: db.findUserById(meta.userId),
62+
client: db.findClientById(meta.clientId),
63+
scope: meta.scope
64+
};
65+
}
66+
67+
async function revokeToken (token) {
68+
db.deleteRefreshToken(token.refreshToken);
69+
70+
return true;
71+
}
72+
73+
async function verifyScope (token, scope) {
74+
if (typeof scope === 'string') {
75+
return scopes.includes(scope);
76+
} else {
77+
return scope.every(s => scopes.includes(s));
78+
}
79+
}
80+
81+
return {
82+
getUser,
83+
getClient,
84+
saveToken,
85+
getAccessToken,
86+
getRefreshToken,
87+
revokeToken,
88+
verifyScope
89+
};
90+
}
91+
92+
module.exports = createModel;

test/helpers/request.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
const Request = require('../../lib/request');
2+
3+
module.exports = (request) => {
4+
const req = new Request({
5+
query: {},
6+
body: {},
7+
headers: {},
8+
method: 'GET',
9+
...request
10+
});
11+
12+
req.is = function (header) {
13+
return this.headers['content-type'] === header;
14+
};
15+
16+
return req;
17+
};

test/integration/flows/model.js

Lines changed: 0 additions & 61 deletions
This file was deleted.

0 commit comments

Comments
 (0)