Skip to content

Commit 7910c4a

Browse files
authored
Merge pull request #21 from HSLdevcom/134-hsl-id
134 hsl-id
2 parents 45b9446 + 5a8747b commit 7910c4a

File tree

16 files changed

+5139
-4158
lines changed

16 files changed

+5139
-4158
lines changed

.env.dev

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22
# Set PG_JORE_CONNECTION_STRING in the environment
33
JORE_GRAPHQL_URL=https://dev.kartat.hsl.fi/jore/graphql
44
GENERATE_API_URL=https://dev.kartat.hsl.fi/map-generator
5-
AZURE_STORAGE_ACCOUNT=secret
6-
AZURE_STORAGE_KEY=secret
5+
AZURE_STORAGE_ACCOUNT=
6+
AZURE_STORAGE_KEY=
77
AZURE_UPLOAD_CONTAINER=routemap-dev
8+
9+
CLIENT_ID=7833861618225795
10+
CLIENT_SECRET=
11+
API_CLIENT_ID=2411181397763460
12+
API_CLIENT_SECRET=
13+
REDIRECT_URI=https://dev.kartat.hsl.fi/kartta
14+
LOGIN_PROVIDER_URI=https://hslid-uat.cinfra.fi
15+
DOMAINS_ALLOWED_TO_GENERATE=
16+
DOMAINS_ALLOWED_TO_LOGIN=

.env.latest

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,12 @@ GENERATE_API_URL=https://prod.kartat.hsl.fi/map-generator
55
AZURE_STORAGE_ACCOUNT=secret
66
AZURE_STORAGE_KEY=secret
77
AZURE_UPLOAD_CONTAINER=routemap-prod
8+
9+
CLIENT_ID=7833861618225795
10+
CLIENT_SECRET=
11+
API_CLIENT_ID=2411181397763460
12+
API_CLIENT_SECRET=
13+
REDIRECT_URI=
14+
LOGIN_PROVIDER_URI=https://hslid-uat.cinfra.fi
15+
DOMAINS_ALLOWED_TO_GENERATE=
16+
DOMAINS_ALLOWED_TO_LOGIN=

.env.production

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,12 @@ GENERATE_API_URL=https://prod.kartat.hsl.fi/map-generator
55
AZURE_STORAGE_ACCOUNT=secret
66
AZURE_STORAGE_KEY=secret
77
AZURE_UPLOAD_CONTAINER=routemap-prod
8+
9+
CLIENT_ID=7833861618225795
10+
CLIENT_SECRET=
11+
API_CLIENT_ID=2411181397763460
12+
API_CLIENT_SECRET=
13+
REDIRECT_URI=https://kartat.hsl.fi/kartta
14+
LOGIN_PROVIDER_URI=https://hslid-uat.cinfra.fi
15+
DOMAINS_ALLOWED_TO_GENERATE=
16+
DOMAINS_ALLOWED_TO_LOGIN=

.env.stage

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,5 @@ GENERATE_API_URL=https://stage.kartat.hsl.fi/map-generator
55
AZURE_STORAGE_ACCOUNT=secret
66
AZURE_STORAGE_KEY=secret
77
AZURE_UPLOAD_CONTAINER=routemap-stage
8+
DOMAINS_ALLOWED_TO_GENERATE=
9+
DOMAINS_ALLOWED_TO_LOGIN=

constants.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,9 @@ module.exports = {
3838
AZURE_UPLOAD_CONTAINER: secretsEnv.AZURE_UPLOAD_CONTAINER || 'routemap-prod',
3939
AZURE_STORAGE_ACCOUNT: secretsEnv.AZURE_STORAGE_ACCOUNT || '',
4040
AZURE_STORAGE_KEY: secretsEnv.AZURE_STORAGE_KEY || '',
41+
CLIENT_SECRET: secretsEnv.CLIENT_SECRET || '',
42+
API_CLIENT_SECRET: secretsEnv.API_CLIENT_SECRET || '',
43+
DOMAINS_ALLOWED_TO_LOGIN: secretsEnv.DOMAINS_ALLOWED_TO_LOGIN || '',
44+
HSL_TESTING_HSLID_USERNAME: secretsEnv.HSL_TESTING_HSLID_USERNAME || '',
45+
HSL_TESTING_HSLID_PASSWORD: secretsEnv.HSL_TESTING_HSLID_PASSWORD || '',
4146
};

package-lock.json

Lines changed: 3536 additions & 3191 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@
9595
"koa": "^2.4.1",
9696
"koa-json-body": "^5.3.0",
9797
"koa-router": "7.3.0",
98+
"koa-session": "^5.10.1",
9899
"lodash": "^4.17.4",
99100
"moment": "^2.19.2",
100101
"node-fetch": "^1.7.3",

scripts/auth/authEndpoints.js

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
const { get, last, clone } = require('lodash');
2+
const AuthService = require('./authService');
3+
4+
const { DOMAINS_ALLOWED_TO_LOGIN } = require('../../constants');
5+
6+
// TODO: Envify
7+
const allowedDomains = DOMAINS_ALLOWED_TO_LOGIN.split(',');
8+
const allowedGroup = 'Karttageneraattori';
9+
10+
const hasAllowedGroup = async userInfo => {
11+
const groupNames = get(userInfo, 'groups');
12+
const domain = last(userInfo.email.toLowerCase().split('@')) || '';
13+
14+
if (groupNames.includes(allowedGroup)) {
15+
return true;
16+
}
17+
18+
if (!allowedDomains.includes(domain)) {
19+
console.log(`User does not have allowed domain. Logging out.`);
20+
return false;
21+
}
22+
23+
if (allowedDomains.includes(domain) && !groupNames.includes(allowedGroup)) {
24+
groupNames.push(allowedGroup);
25+
await AuthService.setGroup(userInfo.userId, groupNames);
26+
}
27+
return true;
28+
};
29+
30+
const authorize = async (req, res, session) => {
31+
const authRequest = req.body;
32+
const modifiedSession = clone(session);
33+
const { isTesting } = authRequest;
34+
35+
if (modifiedSession && isTesting) {
36+
// When testing, code is already an access token (because tests fetched code with password grant request that gives you the correct access token)
37+
modifiedSession.accessToken = authRequest.code;
38+
const userInfo = await AuthService.requestUserInfo(authRequest.code);
39+
modifiedSession.email = userInfo.email;
40+
modifiedSession.groups = userInfo.groups;
41+
return {
42+
status: 200,
43+
body: {
44+
isOk: true,
45+
email: userInfo.email,
46+
},
47+
modifiedSession,
48+
};
49+
}
50+
51+
if (!authRequest.code) {
52+
console.log('No authorization code');
53+
return {
54+
body: {
55+
isOk: false,
56+
},
57+
status: 401,
58+
};
59+
}
60+
const tokenResponse = await AuthService.requestAccessToken(authRequest.code);
61+
62+
if (session && tokenResponse.access_token) {
63+
modifiedSession.accessToken = tokenResponse.access_token;
64+
const userInfo = await AuthService.requestUserInfo(modifiedSession.accessToken);
65+
const isAllowed = await hasAllowedGroup(userInfo);
66+
if (!isAllowed) {
67+
return {
68+
status: 401,
69+
body: {
70+
isOk: false,
71+
message: 'No allowed group.',
72+
},
73+
};
74+
}
75+
76+
modifiedSession.email = userInfo.email;
77+
modifiedSession.groups = userInfo.groups;
78+
79+
const response = {
80+
isOk: true,
81+
email: userInfo.email,
82+
};
83+
84+
return {
85+
status: 200,
86+
body: response,
87+
modifiedSession,
88+
};
89+
}
90+
console.log('No access token: ', tokenResponse);
91+
const response = {
92+
isOk: false,
93+
};
94+
return {
95+
status: 401,
96+
body: response,
97+
};
98+
};
99+
100+
const checkExistingSession = async (req, res, session) => {
101+
if (session && session.accessToken) {
102+
const isAllowed = await hasAllowedGroup(session);
103+
if (!isAllowed) {
104+
await AuthService.logoutFromIdentityProvider(session.accessToken);
105+
return {
106+
status: 200,
107+
};
108+
}
109+
110+
const response = {
111+
isOk: true,
112+
email: session.email,
113+
};
114+
return {
115+
status: 200,
116+
body: response,
117+
};
118+
}
119+
console.log('No existing session');
120+
const response = {
121+
isOk: false,
122+
};
123+
return {
124+
status: 200,
125+
body: response,
126+
};
127+
};
128+
129+
const logout = async (req, res, session) => {
130+
if (session && session.accessToken) {
131+
await AuthService.logoutFromIdentityProvider(session.accessToken);
132+
return {
133+
status: 200,
134+
};
135+
}
136+
return {
137+
status: 401,
138+
};
139+
};
140+
141+
module.exports = {
142+
authorize,
143+
checkExistingSession,
144+
logout,
145+
};

scripts/auth/authService.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
const nodeFetch = require('node-fetch');
2+
3+
const { CLIENT_ID, REDIRECT_URI, LOGIN_PROVIDER_URI, API_CLIENT_ID } = process.env;
4+
5+
const { CLIENT_SECRET, API_CLIENT_SECRET } = require('../../constants');
6+
7+
const authHash = Buffer.from(`${API_CLIENT_ID}:${API_CLIENT_SECRET}`).toString('base64');
8+
9+
const requestAccessToken = async code => {
10+
const url = `${LOGIN_PROVIDER_URI}/openid/token?client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}&grant_type=authorization_code&code=${code}&redirect_uri=${REDIRECT_URI}`;
11+
const response = await nodeFetch(url, {
12+
method: 'POST',
13+
headers: {
14+
Accept: 'application/json',
15+
'Content-Type': 'application/json',
16+
},
17+
});
18+
return response.json();
19+
};
20+
21+
const requestUserInfo = async accessToken => {
22+
const url = `${LOGIN_PROVIDER_URI}/openid/userinfo`;
23+
const response = await nodeFetch(url, {
24+
headers: {
25+
Authorization: `Bearer ${accessToken}`,
26+
},
27+
});
28+
const responseJson = await response.json();
29+
30+
return {
31+
userId: responseJson.sub,
32+
email: responseJson.email,
33+
emailVerified: responseJson.email_verified,
34+
groups: responseJson['https://oneportal.trivore.com/claims/groups'],
35+
};
36+
};
37+
38+
const logoutFromIdentityProvider = async accessToken => {
39+
const url = `${LOGIN_PROVIDER_URI}/openid/logout`;
40+
return nodeFetch(url, {
41+
headers: {
42+
Authorization: `Bearer ${accessToken}`,
43+
},
44+
});
45+
};
46+
47+
const requestGroups = async () => {
48+
const url = `${LOGIN_PROVIDER_URI}/api/rest/v1/group`;
49+
const groupsResponse = await nodeFetch(url, {
50+
method: 'GET',
51+
headers: {
52+
Authorization: `Basic ${authHash}`,
53+
},
54+
});
55+
56+
return groupsResponse.json();
57+
};
58+
59+
const setGroup = async (userId, groupNames) => {
60+
const url = `${LOGIN_PROVIDER_URI}/api/rest/v1/user/${userId}`;
61+
const groups = await requestGroups();
62+
// const matchingSecrets = secrets.filter(secretFile => secretFile.startsWith(key));
63+
64+
const groupIds = [];
65+
groups.resources.forEach(group => {
66+
if (groupNames.includes(group.name)) {
67+
groupIds.push(group.id);
68+
}
69+
});
70+
const response = await nodeFetch(url, {
71+
method: 'PUT',
72+
headers: {
73+
Accept: 'application/json',
74+
'Content-Type': 'application/json',
75+
Authorization: `Basic ${authHash}`,
76+
},
77+
body: JSON.stringify({
78+
memberOf: groupIds,
79+
}),
80+
});
81+
82+
return response.json();
83+
};
84+
85+
module.exports = { requestAccessToken, requestUserInfo, logoutFromIdentityProvider, setGroup };

scripts/server.js

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
const Koa = require('koa');
22
const Router = require('koa-router');
3+
const session = require('koa-session');
34
const cors = require('@koa/cors');
45
const jsonBody = require('koa-json-body');
5-
6+
const authEndpoints = require('./auth/authEndpoints');
67
const generator = require('./generator');
78
const {
89
migrate,
@@ -178,9 +179,43 @@ async function main() {
178179
ctx.body = await getConfig();
179180
});
180181

182+
router.post('/login', async ctx => {
183+
const authResponse = await authEndpoints.authorize(ctx.request, ctx.response, ctx.session);
184+
ctx.session = null;
185+
if (authResponse.modifiedSession) {
186+
ctx.session = authResponse.modifiedSession;
187+
}
188+
ctx.body = authResponse.body;
189+
ctx.response.status = authResponse.status;
190+
});
191+
192+
router.get('/logout', async ctx => {
193+
const authResponse = await authEndpoints.logout(ctx.request, ctx.response, ctx.session);
194+
ctx.session = null;
195+
ctx.response.status = authResponse.status;
196+
});
197+
198+
router.get('/session', async ctx => {
199+
const authResponse = await authEndpoints.checkExistingSession(
200+
ctx.request,
201+
ctx.response,
202+
ctx.session,
203+
);
204+
ctx.body = authResponse.body;
205+
ctx.response.status = authResponse.status;
206+
});
207+
208+
app.keys = ['secret key'];
209+
210+
app.use(session(app));
211+
181212
app
182213
.use(errorHandler)
183-
.use(cors())
214+
.use(
215+
cors({
216+
credentials: true,
217+
}),
218+
)
184219
.use(jsonBody({ fallback: true, limit: '10mb' }))
185220
.use(router.routes())
186221
.use(router.allowedMethods())

0 commit comments

Comments
 (0)