-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathauthsvc.js
146 lines (131 loc) · 5 KB
/
authsvc.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//
// Utility functions for validating a user access token as an optional alternative
// authentication mechanism in the express middleware. If authenticated via OAuth,
// session is cleared and req.user is populated. This will only look for and
// evaluate an access token if the request is not already authenticated via a
// session (e.g. not already an OIDC logged in session).
//
// This is not particularly efficient because no caching is done of access token
// validation results, however we do not expect high volumes of OAuth-authenticated
// traffic to this application. OAuth authentication will only be used by a mobile
// application during credential registration time. As such, no caching logic has
// been implemented.
//
const requestp = require('request-promise-native');
const tm = require('./oauthtokenmanager.js');
/**
* Use the "formatted" display name from SCIM record if available, otherwise fallback
* to username.
*/
function getDisplayNameFromSCIMResponse(scimResponse) {
let result = scimResponse.userName;
if (scimResponse.name != null && scimResponse.name.formatted != null) {
result = scimResponse.name.formatted;
}
return result;
}
/**
* This is a middleware function designed to optionally look for and authenticate
* a request (by populating req.user) if the request is not already an authenticated
* session, and a valid access token is present.
*
* In this scheme, access tokens are like API keys - they are long-lived. Their
* representation is a symmetric encryption of a Cloud Identity user id. If the
* decryption is successful, a lookup of the user record in Cloud Identity is then
* completed to ensure that the access token (there can only be one valid active
* access token for a user) is still "current", and to retrieve other user details.
*
* Note that we don't "fail" the request on validation errors - we just silently
* ignore errors and the request will proceed as unauthenticated. This is a
* concious and required implementation decision of this function.
*/
function validateAccessToken() {
return async (req, rsp, next) => {
if (req.session.username == null) {
let token = null;
let authz = req.get("Authorization");
if (authz !== undefined) {
let parts = authz.split(' ');
if (parts.length == 2) {
let scheme = parts[0]
let credentials = parts[1];
if (/^Bearer$/i.test(scheme)) {
token = credentials;
} else {
console.log("Authorization scheme was not Bearer");
}
} else {
console.log("Invalid Authorization header");
}
if (token != null) {
let isError = false;
let access_token = null;
let userSCIMResponse = null;
let userSCIMId = tm.validateUserAccessToken(token);
if (userSCIMId != null) {
// look up user record to validate AT is current, and get username, etc
try {
access_token = await tm.getAccessToken(req);
} catch (e) {
console.log(e);
isError = true;
}
if (!isError) {
try {
userSCIMResponse = await requestp({
url: process.env.CI_TENANT_ENDPOINT + "/v2.0/Users",
method: "GET",
qs: { "filter" : 'id eq "' + userSCIMId + '"' },
headers: {
"Accept": "application/scim+json",
"Authorization": "Bearer " + access_token
},
json: true
});
} catch (e) {
console.log(e);
isError = true;
}
}
if (!isError) {
//console.log("Validating access token received userSCIMResponse: " + JSON.stringify(userSCIMResponse));
if (userSCIMResponse != null && userSCIMResponse.totalResults == 1) {
let userRecord = userSCIMResponse.Resources[0];
let uat = null;
if (userRecord != null && userRecord["urn:ietf:params:scim:schemas:extension:ibm:2.0:User"] != null
&& userRecord["urn:ietf:params:scim:schemas:extension:ibm:2.0:User"]["customAttributes"] != null) {
for (let i = 0; i < userRecord["urn:ietf:params:scim:schemas:extension:ibm:2.0:User"]["customAttributes"].length && uat == null; i++) {
let ca = userRecord["urn:ietf:params:scim:schemas:extension:ibm:2.0:User"]["customAttributes"][i];
if (ca.name == "userAccessToken") {
uat = ca.values[0];
}
}
}
if (token == uat) {
// all ok - authenticate the request, without a session
req.session = null;
req.user = {
"username": userRecord.userName,
"userDisplayName": getDisplayNameFromSCIMResponse(userRecord),
"userSCIMId": userRecord.id,
"userAccessToken": uat
};
} else {
console.log("Provided access token: " + token + " does not match token in user record: " + uat);
}
}
}
}
}
} else {
// console.log("No Authorization header");
}
} else {
//console.log("User already authenticated, skipping OAuth authentication");
}
return next();
}
}
module.exports = {
validateAccessToken: validateAccessToken
};