Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
bombkyu
login-with-cryptobadge-express-oauth2
Commits
453fdbe0
Commit
453fdbe0
authored
6 years ago
by
Donghwan Kim
Browse files
Options
Download
Email Patches
Plain Diff
Trim again and again
parent
02b40295
master
handler-authorization-endpoint-error-case
nullable-scope
support-pkce
testing
trim-again-and-again
2 merge requests
!12
Support PKCE as well as state param
,
!11
Trim again and again
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
server/index.js
+73
-85
server/index.js
server/package-lock.json
+2
-3
server/package-lock.json
server/package.json
+1
-1
server/package.json
server/views/permission.ejs
+1
-1
server/views/permission.ejs
with
77 additions
and
90 deletions
+77
-90
server/index.js
View file @
453fdbe0
...
...
@@ -18,32 +18,37 @@ const SCOPES = [
// ## DynamoDB integration
// `User`, `Application`, `AuthorizationCode`, and `Token` are models to implement OAuth2 protocol.
// `User`, `Application`, `
OAuth2Client`, `OAuth2
AuthorizationCode`, and `
OAuth2
Token` are models to implement OAuth2 protocol.
// Port the models to DynamoDB. Of course, contents are fake.
const
User
=
[
// This User schema doesn't mean much.
{
id
:
'
1
'
,
username
:
'
bob
'
,
password
:
'
secret
'
,
name
:
'
Bob Smith
'
,
email
:
'
bob@gmail.com
'
},
{
id
:
'
2
'
,
username
:
'
joe
'
,
password
:
'
password
'
,
name
:
'
Joe Davis
'
,
email
:
'
Joe@gmail.com
'
},
];
// Application and OAuth2Client are supposed to be created from CryptoBadge Developer website
const
Application
=
[
{
"
id
"
:
"
1
"
,
"
name
"
:
"
Vingle
"
,
// unique
"
clientId
"
:
"
1b0fbe1836d2133ba3c3d3475b3bad9acae57ff6
"
,
// nullable
"
clientSecret
"
:
"
68529ae2c23361ebd5f4946f09c1cf36f44bb580
"
,
}
];
const
OAuth2Client
=
[
{
// Id
"
id
"
:
"
1b0fbe1836d2133ba3c3d3475b3bad9acae57ff6
"
,
"
secret
"
:
"
68529ae2c23361ebd5f4946f09c1cf36f44bb580
"
,
"
redirectUris
"
:
[
"
http://localhost:3002/auth/cryptobadge/callback
"
],
"
grant
Type
s
"
:
[
"
grants
"
:
[
"
authorization_code
"
,
"
refresh_token
"
,
]
"
refresh_token
"
],
"
applicationId
"
:
"
1
"
}
];
// Created by `saveAuthorizationCode` and deleted by `revokeAuthorizationCode`
const
AuthorizationCode
=
[
const
OAuth2
AuthorizationCode
=
[
// {
// "id": "1",
// "authorizationCode": "abaf922582d8001c9fc5ff11f0a453334fafca3d",
...
...
@@ -55,7 +60,7 @@ const AuthorizationCode = [
// }
];
// Created by `saveToken` and deleted by `revokeToken`
const
Token
=
[
const
OAuth2
Token
=
[
// {
// "id": "1",
// "accessToken": "65e7698f665959e6cff663264c85d481edb25f66",
...
...
@@ -95,7 +100,7 @@ const OAuth2Adapter = {
// 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
);
const
answer
=
await
OAuth2
Token
.
find
(
token
=>
token
.
accessToken
===
accessToken
);
if
(
answer
!=
null
)
{
answer
.
client
=
{
id
:
answer
.
clientId
};
answer
.
user
=
{
id
:
answer
.
userId
};
...
...
@@ -104,7 +109,7 @@ const OAuth2Adapter = {
return
answer
;
},
getRefreshToken
:
async
(
refreshToken
)
=>
{
const
answer
=
await
Token
.
find
(
token
=>
token
.
refreshToken
===
refreshToken
);
const
answer
=
await
OAuth2
Token
.
find
(
token
=>
token
.
refreshToken
===
refreshToken
);
if
(
answer
!=
null
)
{
answer
.
client
=
{
id
:
answer
.
clientId
};
answer
.
user
=
{
id
:
answer
.
userId
};
...
...
@@ -113,7 +118,7 @@ const OAuth2Adapter = {
return
answer
;
},
getAuthorizationCode
:
async
(
authorizationCode
)
=>
{
const
answer
=
await
AuthorizationCode
.
find
(
code
=>
code
.
authorizationCode
===
authorizationCode
);
const
answer
=
await
OAuth2
AuthorizationCode
.
find
(
code
=>
code
.
authorizationCode
===
authorizationCode
);
if
(
answer
!=
null
)
{
answer
.
client
=
{
id
:
answer
.
clientId
};
answer
.
user
=
{
id
:
answer
.
userId
};
...
...
@@ -122,39 +127,18 @@ const OAuth2Adapter = {
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
)
{
const
answer
=
await
OAuth2Client
.
find
(
client
=>
client
.
id
===
clientId
);
if
(
answer
!=
null
&&
clientSecret
&&
clientSecret
!==
answer
.
secret
)
{
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
,
};
return
answer
;
},
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
}
`
,
id
:
`
${
OAuth2
Token
.
length
+
1
}
`
,
// String
accessToken
:
token
.
accessToken
,
// Date
...
...
@@ -171,7 +155,7 @@ const OAuth2Adapter = {
userId
:
user
.
id
};
await
Token
.
push
(
answer
);
await
OAuth2
Token
.
push
(
answer
);
return
{...
answer
,
client
:
{
id
:
answer
.
clientId
},
user
:
{
id
:
answer
.
userId
}};
},
...
...
@@ -179,7 +163,7 @@ const OAuth2Adapter = {
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
}
`
,
id
:
`
${
OAuth2
AuthorizationCode
.
length
+
1
}
`
,
// String
authorizationCode
:
code
.
authorizationCode
,
// Date
...
...
@@ -194,14 +178,14 @@ const OAuth2Adapter = {
userId
:
user
.
id
};
await
AuthorizationCode
.
push
(
answer
);
await
OAuth2
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
);
const
index
=
await
OAuth2
Token
.
findIndex
(
token
=>
token
.
id
===
id
);
if
(
index
!==
-
1
)
{
await
Token
.
splice
(
index
,
1
);
await
OAuth2
Token
.
splice
(
index
,
1
);
// If the revocation was successful
return
true
;
}
...
...
@@ -210,9 +194,9 @@ const OAuth2Adapter = {
return
false
;
},
revokeAuthorizationCode
:
async
({
id
})
=>
{
const
index
=
await
AuthorizationCode
.
findIndex
(
code
=>
code
.
id
===
id
);
const
index
=
await
OAuth2
AuthorizationCode
.
findIndex
(
code
=>
code
.
id
===
id
);
if
(
index
!==
-
1
)
{
await
AuthorizationCode
.
splice
(
index
,
1
);
await
OAuth2
AuthorizationCode
.
splice
(
index
,
1
);
// If the revocation was successful
return
true
;
}
...
...
@@ -232,10 +216,32 @@ app.use(bodyParser.json());
app
.
use
(
bodyParser
.
urlencoded
({
extended
:
false
}));
// ## CryptoBadge Integration
// Port the following routes to cryptobadge.app properly
// ## CryptoBadge integration (Authorization server)
const
oauth
=
new
OAuth2Server
({
model
:
OAuth2Adapter
});
const
handleResponse
=
function
(
req
,
res
,
response
)
{
// TODO should set response from the Lambda Function
if
(
response
.
status
===
302
)
{
const
location
=
response
.
headers
.
location
;
delete
response
.
headers
.
location
;
res
.
set
(
response
.
headers
);
res
.
redirect
(
location
);
}
else
{
res
.
set
(
response
.
headers
);
res
.
status
(
response
.
status
).
send
(
response
.
body
);
}
};
const
handleError
=
function
(
e
,
req
,
res
,
response
)
{
if
(
response
)
{
res
.
set
(
response
.
headers
);
}
res
.
status
(
e
.
code
);
if
(
e
instanceof
UnauthorizedRequestError
)
{
return
res
.
send
();
}
res
.
send
({
error
:
e
.
name
,
error_description
:
e
.
message
});
};
// Port the routes to cryptobadge.app properly
// ### `GET /oauth2/authorize` - Authorization Endpoint: https://tools.ietf.org/html/rfc6749#section-3.1
app
.
get
(
'
/oauth2/authorize
'
,
async
(
req
,
res
)
=>
{
// `req.session.user` is set if a user signs in and is removed if a user signs out
...
...
@@ -243,9 +249,10 @@ app.get('/oauth2/authorize', async (req, res) => {
return
res
.
redirect
(
`/login?redirect=
${
encodeURIComponent
(
req
.
originalUrl
)}
`
);
}
const
application
=
Application
.
find
((
app
)
=>
app
.
clientId
==
req
.
query
.
client_id
);
const
client
=
OAuth2Client
.
find
((
client
)
=>
client
.
id
===
req
.
query
.
client_id
);
const
application
=
Application
.
find
((
app
)
=>
app
.
id
==
client
.
applicationId
);
// If a user has never approved this client, he/she should decide whether to approve it.
const
token
=
Token
.
find
((
token
)
=>
token
.
clientId
===
req
.
query
.
client_id
&&
token
.
userId
===
req
.
session
.
user
.
id
);
const
token
=
OAuth2
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
...
...
@@ -255,7 +262,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
,
client
Name
:
application
.
name
,
application
Name
:
application
.
name
,
client_id
:
req
.
query
.
client_id
,
scope
:
req
.
query
.
scope
,
ticket
,
...
...
@@ -335,8 +342,6 @@ app.post('/oauth2/token', async (req, res) => {
handleError
(
e
,
req
,
res
,
response
);
}
});
// #######
// These routes are only for this example so not that meaningful.
app
.
get
(
'
/login
'
,
(
req
,
res
)
=>
{
res
.
render
(
'
login
'
,
{
redirect
:
req
.
query
.
redirect
})
...
...
@@ -355,50 +360,33 @@ app.post('/login', (req, res) => {
// #######
// ## User API
// ##
Sample
User API
(Resource server)
app
.
post
(
'
/api/user
'
,
async
(
req
,
res
)
=>
{
// Authenticates the request
const
request
=
new
Request
({
headers
:
req
.
headers
,
method
:
req
.
method
,
query
:
req
.
query
,
});
const
response
=
new
Response
();
let
token
;
try
{
const
token
=
await
oauth
.
authenticate
(
request
,
response
);
// TODO revisit user.xx
const
{
scope
,
user
}
=
token
;
const
info
=
{
user
:
user
.
username
};
if
(
scope
.
includes
(
'
email
'
))
{
info
.
email
=
user
.
email
;
}
res
.
send
(
info
);
token
=
await
oauth
.
authenticate
(
request
,
response
);
}
catch
(
e
)
{
handleError
(
e
,
req
,
res
,
response
);
// TODO replace this handleError with other one since this route belongs to the resource server
return
handleError
(
e
,
req
,
res
,
response
);
}
// Now that authentication succeeded, handle the request
const
{
userId
,
scope
}
=
token
;
const
user
=
User
.
find
(
user
=>
user
.
id
===
userId
);
delete
user
.
password
;
if
(
!
scope
.
split
(
'
'
).
includes
(
'
email
'
))
{
delete
user
.
email
;
}
res
.
send
(
user
);
});
// #######
app
.
listen
(
4000
);
const
handleResponse
=
function
(
req
,
res
,
response
)
{
// TODO should set response from the Lambda Function
if
(
response
.
status
===
302
)
{
const
location
=
response
.
headers
.
location
;
delete
response
.
headers
.
location
;
res
.
set
(
response
.
headers
);
res
.
redirect
(
location
);
}
else
{
res
.
set
(
response
.
headers
);
res
.
status
(
response
.
status
).
send
(
response
.
body
);
}
};
const
handleError
=
function
(
e
,
req
,
res
,
response
)
{
if
(
response
)
{
res
.
set
(
response
.
headers
);
}
res
.
status
(
e
.
code
);
if
(
e
instanceof
UnauthorizedRequestError
)
{
return
res
.
send
();
}
res
.
send
({
error
:
e
.
name
,
error_description
:
e
.
message
});
};
This diff is collapsed.
Click to expand it.
server/package-lock.json
View file @
453fdbe0
...
...
@@ -321,9 +321,8 @@
"integrity"
:
"sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk="
},
"oauth2-server"
:
{
"version"
:
"3.0.1"
,
"resolved"
:
"https://registry.npmjs.org/oauth2-server/-/oauth2-server-3.0.1.tgz"
,
"integrity"
:
"sha512-LFAT4MeTaOgdW+b8YMVMsPhJ8LrbSfVkYZRPgRmELJEJoXcchb/L4b9/lEmgpeNtjH8PlFiqof+YwI+y/oJuOg=="
,
"version"
:
"git+https://github.com/flowersinthesand/node-oauth2-server.git#875fdb990c149210f2f3fd409303070f907eab1a"
,
"from"
:
"git+https://github.com/flowersinthesand/node-oauth2-server.git#875fdb990c149210f2f3fd409303070f907eab1a"
,
"requires"
:
{
"basic-auth"
:
"^2.0.0"
,
"bluebird"
:
"^3.5.1"
,
...
...
This diff is collapsed.
Click to expand it.
server/package.json
View file @
453fdbe0
...
...
@@ -15,6 +15,6 @@
"cookie-session"
:
"^2.0.0-beta.3"
,
"ejs"
:
"^2.6.1"
,
"express"
:
"^4.16.4"
,
"oauth2-server"
:
"
^3.0.1
"
"oauth2-server"
:
"
git+https://github.com/flowersinthesand/node-oauth2-server.git#875fdb990c149210f2f3fd409303070f907eab1a
"
}
}
This diff is collapsed.
Click to expand it.
server/views/permission.ejs
View file @
453fdbe0
...
...
@@ -3,7 +3,7 @@
<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<p>Hello,</p>
<p><b><%=
client
Name %></b> is requesting access to your account.</p>
<p><b><%=
application
Name %></b> is requesting access to your account.</p>
<p><b><%= scope %></b></p>
<p>Do you approve?</p>
<form action="/oauth2/authorize" method="post">
...
...
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment
Menu
Projects
Groups
Snippets
Help