UNPKG

11.3 kBPlain TextView Raw
1/**
2 * Created by frank.zickert on 28.02.19.
3 */
4declare var require: any;
5
6
7import jwt from 'jsonwebtoken';
8
9import {getBasename} from '../libs/iso-libs';
10/**
11 * token handed over to the user's browser, serves as password to encrypt/decrypt the Medium-access token
12 * @type {string}
13 */
14export const IC_WEB_TOKEN = "IC_WEB_TOKEN";
15
16/**
17 * unique id of the user, comes from the provider (GitHub, Medium, etc)
18 * @type {string}
19 */
20export const IC_USER_ID = 'IC_USER_ID';
21
22export const EMAIL_CONFIRMATION_PARAM = "confirmationtoken";
23export const EMAIL_PARAM = "email";
24export const PASSWORD_PARAM = "password";
25
26export const AUTH_STATUS = {
27 PENDING: "pending", // the authentication is pending, e.g. e-mail waitung for the confirmation
28 ACTIVE: "active" // the authentication is active
29}
30
31
32/**
33 * This is an Express middleware that checks whether there is a cookie in the header that contains valid login
34 * data
35 *
36 * @param req
37 * @param res
38 * @param next
39 * @returns if successful, it calls the next middleware, if not, it throws an exception that causes the
40 * next error handler to be called
41 */
42export const createAuthMiddleware = (clientSecret, onAuthenticated: (userid:string) => void) => (req, res, next) => {
43
44 console.log("createAuthMiddleware", req.universalCookies);
45
46 const webtoken = req.universalCookies.get(IC_WEB_TOKEN);
47 const userId = req.universalCookies.get(IC_USER_ID);
48
49 if (webtoken !== undefined && userId !== undefined) {
50 console.log("webtoken: ", webtoken);
51 console.log("userId: ", userId);
52
53 try {
54 const decoded = jwt.verify(webtoken, clientSecret);
55 if (decoded !== undefined) {
56
57 const { id } = decoded;
58
59 console.log("id: ", id);
60
61 // we might have numbers... then the "===" comparison does not work
62 if (id.toString() === userId.toString()) {
63 // the token contains the correct id
64 console.log("token matches :-)")
65 onAuthenticated(id.toString());
66 return next();
67 }
68
69 }
70 return next("UserId in Token does not match UserId in cookie");
71 //throw new Error("UserId in Token does not match UserId in cookie");
72 } catch(err) {
73 return next(err);
74 //throw new Error(err);
75 }
76
77 } else {
78 return next('No token present!');
79 //throw new Error('No token present!');
80 }
81
82
83
84
85};
86
87export interface IUserData {
88 id: string | undefined,
89 name: string | undefined,
90 username: string | undefined,
91 imageUrl: string | undefined,
92 email: string | undefined,
93 access_token: string | undefined,
94 encrypted_password?: string,
95 status?: string
96}
97
98const getEncryptedAccessToken = (id, clientSecret, access_token) => {
99
100 const today = new Date();
101 const expirationDate = new Date(today);
102 expirationDate.setDate(today.getDate() + 60);
103
104 // we use the clientSecret to sign the webtoken
105 const webtoken = jwt.sign({
106 id: id,
107 exp: expirationDate.getTime() / 1000,
108 }, clientSecret);
109
110 // now let's use the webtoken to encrypt the access token
111 const encryptedAccessToken = jwt.sign({
112 id: id,
113 accessToken: access_token,
114 exp: expirationDate.getTime() / 1000,
115 }, webtoken);
116
117 return {
118 webtoken: webtoken,
119 encryptedAccessToken: encryptedAccessToken
120 };
121};
122
123/**
124 * Use this middleware at the endpoint that is specified as the callback-url.
125 *
126 * @param fetchAccessToken function that can be called to fetch the access Token
127 * @param getUserData function to get the userData, takes as input the response from the accessToken-request
128 * @param clientSecret
129 * @param callbackUrl
130 * @param storeAuthData
131 * @returns {any}
132 */
133export const createCallbackMiddleware = (
134 clientSecret,
135 fetchAccessToken: (req: any) => any,
136 getUserData: (resJson: any) => Promise<IUserData>,
137 storeAuthData: (request: any, key: string, val: any, jsonData: any) => void,
138 getAuthData: (request: any, matchBrowserIdentity: boolean, key: string, val: any) => any
139) => async function (req, res, next) {
140
141 const path = require('path');
142
143 console.log("THIS IS THE AUTH CALLBACK");
144
145
146 // we use this middleware also as endpoint for email confirmation, then the token-parameter must be specified
147 const email_confirmation = req.query[EMAIL_CONFIRMATION_PARAM];
148 const email_param = req.query[EMAIL_PARAM];
149 const password_param = req.query[PASSWORD_PARAM];
150 const page = req.query["page"];
151
152 console.log("received params: ", email_confirmation, email_param, password_param);
153
154 if (email_param) {
155 // get the entry of the database
156
157 const authDataList = await getAuthData(
158 req, // request: any
159 false, //matchBrowserIdentity -- we do not want to match the browser identity, the user might use another browser to confirm he mail address
160 IC_USER_ID, // key: string
161 email_param//val: any,
162 );
163
164 console.log("retrieved auth-data-list: ", authDataList);
165
166 // check whether the user already exists
167 const parsedAuthDataList = authDataList.map(raw=> JSON.parse(raw.jsonData));
168
169 // the user logs in with her email and password
170 if (password_param !== undefined && parsedAuthDataList.length > 0) {
171
172 const authData = parsedAuthDataList
173 .reduce((result, cur) => result !== undefined ? result : (
174 // check whether the password is correct
175 cur.encrypted_password === password_param ? cur: undefined
176 ), undefined);
177
178 if (authData !== undefined) {
179
180 // create a new webtoken, i.e. other browser will be logged out!
181 const { webtoken, encryptedAccessToken } = getEncryptedAccessToken(email_param, clientSecret, password_param);
182
183 // put the encrypted web token into the database, this is user (browser)-specific data!
184 const storeResult = await storeAuthData(
185 req, // request: any
186 IC_USER_ID, // key: string
187 email_param, //val: any,
188 Object.assign({}, authData, {
189 encryptedAccessToken: encryptedAccessToken
190 })
191 );
192
193
194 req.universalCookies.set(IC_WEB_TOKEN, webtoken, { path: '/' });
195 req.universalCookies.set(IC_USER_ID, email_param, { path: '/' });
196
197
198 console.log("store password verified result: ", storeResult);
199
200 res.redirect(`${path.join(getBasename(), page !== undefined ? page : "/")}?message=success`);
201
202
203 } else {
204 console.log ("could not verify password, ", password_param,email_param);
205 return next("login failure");
206 }
207
208 return;
209
210 } else if (email_confirmation && parsedAuthDataList.length > 0) {
211 // the user clicks the link from within the confirmation email
212
213 const authData = parsedAuthDataList
214 .reduce((result, cur) => result !== undefined ? result : (
215 cur.encryptedAccessToken === email_confirmation ? cur: undefined
216 ), undefined);
217
218 console.log("retrieved auth-data: ", authData);
219
220 if (authData !== undefined) {
221
222 const { webtoken, encryptedAccessToken } = getEncryptedAccessToken(email_param, clientSecret, email_confirmation);
223
224
225 // put the encrypted web token into the database, this is user (browser)-specific data!
226 const storeResult = await storeAuthData(
227 req, // request: any
228 IC_USER_ID, // key: string
229 email_param, //val: any,
230 Object.assign({}, authData, {
231 status: AUTH_STATUS.ACTIVE,
232 encryptedAccessToken: encryptedAccessToken
233 })
234 );
235
236 console.log("webtoken: ", webtoken, email_param)
237
238 req.universalCookies.set(IC_WEB_TOKEN, webtoken, { path: '/' });
239 req.universalCookies.set(IC_USER_ID, email_param, { path: '/' });
240
241 console.log("store email verified result: ", storeResult);
242
243 res.redirect(`${path.join(getBasename(), page !== undefined ? page : "/")}?message=mailverified`);
244
245
246 } else {
247 console.log ("could not verify access token, ", email_confirmation,email_param);
248
249 return next("access token is wrong");
250 }
251 return;
252 }
253
254 }
255
256 const { redirectPage, fFetch } = fetchAccessToken(req);
257
258 // store the redirectPage in the request for further processing
259 console.log("redirect to: ", redirectPage);
260 req["redirectPage"] = redirectPage;
261
262
263 await fFetch().then(async function(resJson) {
264
265 //const { token_type, access_token /*, refresh_token, scope, expires_at */} = resJson;
266
267 // try the freshly acquired token and get the user's Medium.com id
268 await getUserData(resJson).then(async function(data) {
269 console.log("get user data: ", JSON.stringify(data));
270
271 const {id, name, username, imageUrl, access_token, email, status } = data;
272
273 console.log("id: ", id);
274 console.log("name: ", name);
275
276 const { webtoken, encryptedAccessToken } = getEncryptedAccessToken(id, clientSecret, access_token);
277
278 //console.log("encryptedAccessToken: ", encryptedAccessToken);
279
280 // TODO id may be undefined when the token expired!
281
282 //console.log("storeAuthData: ", storeAuthData)
283
284 // put the encrypted web token into the database, this is user (browser)-specific data!
285 const storeResult = await storeAuthData(
286 req, // request: any
287 IC_USER_ID, // key: string
288 id, //val: any,
289 Object.assign({
290 /** We only store the encrypted token when we have an active status, i.e. a auth-provider
291 * we keep it in clear-text for e-mail */
292 encryptedAccessToken: status === AUTH_STATUS.ACTIVE ? encryptedAccessToken : access_token,
293 name: name,
294 username: username,
295 imageUrl: imageUrl,
296 email: email,
297 status: status,
298
299 }, password_param ? {
300 encrypted_password: password_param
301 } : {}) //jsonData: any
302 );
303
304 console.log("storeResult: ", storeResult);
305
306
307 // give the webtoken to back to the user - if the account is valid, only!
308 if (status === AUTH_STATUS.ACTIVE) {
309 req.universalCookies.set(IC_WEB_TOKEN, webtoken, { path: '/' });
310 req.universalCookies.set(IC_USER_ID, id, { path: '/' });
311
312 }
313
314
315 console.log("done") //'http://' +path.join(req.headers.host + +
316 res.redirect(path.join(getBasename(), redirectPage));
317 return;
318 });
319
320 });
321
322
323};
\No newline at end of file