Commit 02b40295 authored by bombkyu's avatar bombkyu
Browse files

Merge branch 'define-model' into 'master'

Define models

See merge request !9
Showing with 166 additions and 35 deletions
+166 -35
......@@ -43,3 +43,12 @@ A `/server` process and `/client` process will listen to `4000` and `3002` port,
- The resource server authenticates the request according to the attached access toekn and decides to return an email (if the email belongs to the resource owner) or null (if the email doesn't belong to the resource owner).
To analyze the flow, focus on `/server/index.js` and `/client/index.js`.
---
#### How to refresh token
```sh
curl -X POST -H 'Content-Type: application/x-www-form-urlencoded' -d 'grant_type=refresh_token&client_id=1b0fbe1836d2133ba3c3d3475b3bad9acae57ff6&client_secret=68529ae2c23361ebd5f4946f09c1cf36f44bb580&refresh_token=47ad5e82afafd0289bbd7ad914674a1f41176cbd' http://localhost:4000/oauth2/token
```
......@@ -26,25 +26,50 @@ const User = [
{id: '2', username: 'joe', password: 'password', name: 'Joe Davis', email: 'Joe@gmail.com'},
];
const Application = [
// TODO revisit
{
// Both id and clientId are required but it seems to be fixed in some commit on the remote dev branch
id: '1b0fbe1836d2133ba3c3d3475b3bad9acae57ff6',
clientId: '1b0fbe1836d2133ba3c3d3475b3bad9acae57ff6',
name: 'Vingle',
// Only applications with confidential backend has secret
clientSecret: '68529ae2c23361ebd5f4946f09c1cf36f44bb580',
redirectUris: ['http://localhost:3002/auth/cryptobadge/callback'],
grants: ['authorization_code']
"id": "1",
"name": "Vingle",
// unique
"clientId": "1b0fbe1836d2133ba3c3d3475b3bad9acae57ff6",
// nullable
"clientSecret": "68529ae2c23361ebd5f4946f09c1cf36f44bb580",
"redirectUris": [
"http://localhost:3002/auth/cryptobadge/callback"
],
"grantTypes": [
"authorization_code",
"refresh_token",
]
}
];
// Created by `saveAuthorizationCode` and deleted by `revokeAuthorizationCode`
const AuthorizationCode = [];
const AuthorizationCode = [
// {
// "id": "1",
// "authorizationCode": "abaf922582d8001c9fc5ff11f0a453334fafca3d",
// "expiresAt": "2019-02-27T05:29:00.189Z",
// "redirectUri": "http://localhost:3002/auth/cryptobadge/callback",
// "scope": "email",
// "clientId": "1b0fbe1836d2133ba3c3d3475b3bad9acae57ff6",
// "userId": "1"
// }
];
// Created by `saveToken` and deleted by `revokeToken`
const Token = [];
const Token = [
// {
// "id": "1",
// "accessToken": "65e7698f665959e6cff663264c85d481edb25f66",
// "accessTokenExpiresAt": "2019-02-27T06:24:00.205Z",
// "refreshToken": "7230354dbe43917167661a25212d599c58432dca",
// "refreshTokenExpiresAt": "2019-05-28T05:24:00.205Z",
// "scope": "email",
// "clientId": "1b0fbe1836d2133ba3c3d3475b3bad9acae57ff6",
// "userId": "1"
// }
];
// `OAuth2Adapter` is an adapter object for 'oauth2-server' package.
// Rewrite it using integrated models with DynamoDB.
// Rewrite it using the above models.
const OAuth2Adapter = {
generateAccessToken: generateRandomToken,
generateRefreshToken: generateRandomToken,
......@@ -67,36 +92,132 @@ const OAuth2Adapter = {
},
// From here the following methods are related with the above models.
getAccessToken: (accessToken) => Token.find(token => token.accessToken === accessToken),
getRefreshToken: (refreshToken) => Token.find(token => token.refreshToken === refreshToken),
getAuthorizationCode: (authorizationCode) => AuthorizationCode.find(code => code.authorizationCode === authorizationCode),
getClient: (clientId, clientSecret) => Application.find(client => client.clientId === clientId || (client.clientSecret && client.clientSecret === clientSecret)),
saveToken: (token, client, user) => {
const answer = {...token, client, user};
Token.push(answer);
// FYI, `async` and `await` keywords are acutally meaningless.
// I used them simply to emphasize what you should do
getAccessToken: async (accessToken) => {
const answer = await Token.find(token => token.accessToken === accessToken);
if (answer != null) {
answer.client = {id: answer.clientId};
answer.user = {id: answer.userId};
}
return answer;
},
saveAuthorizationCode: (code, client, user) => {
const answer = {...code, client, user};
AuthorizationCode.push(answer);
getRefreshToken: async (refreshToken) => {
const answer = await Token.find(token => token.refreshToken === refreshToken);
if (answer != null) {
answer.client = {id: answer.clientId};
answer.user = {id: answer.userId};
}
return answer;
},
revokeToken: (refreshToken) => {
const index = Token.findIndex(token => token.refreshToken === refreshToken);
getAuthorizationCode: async (authorizationCode) => {
const answer = await AuthorizationCode.find(code => code.authorizationCode === authorizationCode);
if (answer != null) {
answer.client = {id: answer.clientId};
answer.user = {id: answer.userId};
}
return answer;
},
getClient: async (clientId, clientSecret) => {
const app = await Application.find(app => {
if (app.clientId !== clientId) {
return false;
}
// If the client has a secret (confidential client), the client secret should be checked too.
if (app.secret != null && (app.secret !== clientSecret)) {
return false;
}
return true;
});
if (app == null) {
return null;
}
return {
// String
id: app.clientId,
// TODO revisit this is workaround and it seems to be fixed in some commit
// http://git.baikal.io/bombkyu/login-with-cryptobadge-express-oauth2/merge_requests/6
// String
clientId: app.clientId,
// Array<String>
redirectUris: app.redirectUris,
// Array<String>
grants: app.grantTypes,
};
},
saveToken: async (token, client, user) => {
const answer = {
// Auto-increment id string
// 'id' is not required by oauth2-server but used to identify a token to revoke
id: `${Token.length + 1}`,
// String
accessToken: token.accessToken,
// Date
accessTokenExpiresAt: token.accessTokenExpiresAt,
// String
refreshToken: token.refreshToken,
// Date
refreshTokenExpiresAt: token.refreshTokenExpiresAt,
// String
scope: token.scope,
// String
clientId: client.id,
// String
userId: user.id
};
await Token.push(answer);
return {...answer, client: {id: answer.clientId}, user: {id: answer.userId}};
},
saveAuthorizationCode: async (code, client, user) => {
const answer = {
// Auto-increment id string
// 'id' is not required by oauth2-server but used to identify a code to revoke
id: `${AuthorizationCode.length + 1}`,
// String
authorizationCode: code.authorizationCode,
// Date
expiresAt: code.expiresAt,
// String
redirectUri: code.redirectUri,
// String
scope: code.scope,
// String
clientId: client.id,
// String
userId: user.id
};
await AuthorizationCode.push(answer);
return {...answer, client: {id: answer.clientId}, user: {id: answer.userId}};
},
revokeToken: async ({id}) => {
const index = await Token.findIndex(token => token.id === id);
if (index !== -1) {
Token.splice(index, 1);
await Token.splice(index, 1);
// If the revocation was successful
return true;
}
// if the refresh token could not be found
return false;
},
revokeAuthorizationCode: (authorizationCode) => {
const index = AuthorizationCode.find(code => code.authorizationCode === authorizationCode);
revokeAuthorizationCode: async ({id}) => {
const index = await AuthorizationCode.findIndex(code => code.id === id);
if (index !== -1) {
AuthorizationCode.splice(index, 1);
await AuthorizationCode.splice(index, 1);
// If the revocation was successful
return true;
}
// if the authorization code could not be found
return false;
},
};
......@@ -111,7 +232,8 @@ app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
// ## Lambda integration
// ## CryptoBadge Integration
// Port the following routes to cryptobadge.app properly
const oauth = new OAuth2Server({ model: OAuth2Adapter });
// ### `GET /oauth2/authorize` - Authorization Endpoint: https://tools.ietf.org/html/rfc6749#section-3.1
......@@ -121,9 +243,9 @@ app.get('/oauth2/authorize', async (req, res) => {
return res.redirect(`/login?redirect=${encodeURIComponent(req.originalUrl)}`);
}
const client = Application.find((client) => client.id == req.query.client_id);
const application = Application.find((app) => app.clientId == req.query.client_id);
// If a user has never approved this client, he/she should decide whether to approve it.
const token = Token.find((token) => token.client.id === req.query.client_id && token.user.id === req.session.user.id);
const token = Token.find((token) => token.clientId === req.query.client_id && token.userId === req.session.user.id);
if (token == null) {
// A ticket is an one-time token to protect `POST /oauth2/authorize`
// You need to replace the below stub with one to generate an one-time token using JWT or something
......@@ -133,7 +255,7 @@ app.get('/oauth2/authorize', async (req, res) => {
// In this page, a user will review what scopes a client want and decide whether to approve it.
return res.render('permission', {
response_type: req.query.response_type,
clientName: client.name,
clientName: application.name,
client_id: req.query.client_id,
scope: req.query.scope,
ticket,
......@@ -150,6 +272,7 @@ app.get('/oauth2/authorize', async (req, res) => {
await oauth.authorize(request, response, {
authenticateHandler: {
handle: () => {
// This object to be returned should have a String type of `id` property. e.g. {id: "34f1b3b9-4882-439a-984f-05f8b41f0576"}
return req.session.user;
}
},
......@@ -182,6 +305,7 @@ app.post('/oauth2/authorize', async (req, res) => {
await oauth.authorize(request, response, {
authenticateHandler: {
handle: () => {
// This object to be returned should have a String type of `id` property. e.g. {id: "34f1b3b9-4882-439a-984f-05f8b41f0576"}
return req.session.user;
}
},
......@@ -213,9 +337,7 @@ app.post('/oauth2/token', async (req, res) => {
});
// #######
// ## CryptoBadge Integration
// These routes are only for this example so not that meaningful. Port them to cryptobadge.app properly
// These routes are only for this example so not that meaningful.
app.get('/login', (req, res) => {
res.render('login', {redirect: req.query.redirect})
});
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment