1 | import React, {ReactNode} from 'react';
|
2 |
|
3 | import {IComponent} from "../types/component";
|
4 | import Types, { IInfrastructure } from "../types";
|
5 |
|
6 | import createMiddleware, { isMiddleware } from '../middleware/middleware-component';
|
7 | import createWebApp, { isWebApp } from '../webapp/webapp-component';
|
8 | import { isSecuredRoute } from './securedroute-component';
|
9 | import { isSecuredEntry } from './securedentry-component';
|
10 | import createRoute, { ROUTE_INSTANCE_TYPE } from '../route/route-component';
|
11 | import { getChildrenArray, findComponentRecursively } from '../libs';
|
12 |
|
13 | import {getBasename} from '../libs/iso-libs';
|
14 |
|
15 | import {
|
16 | createAuthMiddleware,
|
17 | createCallbackMiddleware,
|
18 | IUserData,
|
19 | EMAIL_CONFIRMATION_PARAM,
|
20 | EMAIL_PARAM,
|
21 | PASSWORD_PARAM,
|
22 | AUTH_STATUS
|
23 | } from "./auth-middleware";
|
24 |
|
25 | import bodyParser from 'body-parser';
|
26 | import {isSecuredService} from "./securedservice-component";
|
27 | import {SERVICE_INSTANCE_TYPE} from "../service/service-component";
|
28 |
|
29 | export const AUTHENTICATION_INSTANCE_TYPE = "AuthenticationComponent";
|
30 |
|
31 |
|
32 | export const AuthenticationProvider = {
|
33 | EMAIL: "AUTH_EMAIL",
|
34 |
|
35 | GITHUB: "AUTH_GITHUB",
|
36 |
|
37 |
|
38 | MEDIUM: "AUTH_MEDIUM"
|
39 | };
|
40 |
|
41 | export const AUTH_RESPONSE = {
|
42 | EMAIL_INVALID: "EMAIL_INVALID",
|
43 | NOT_IMPLEMENTED: "NOT_IMPLEMENTED"
|
44 | };
|
45 |
|
46 | export const getProviderKey = (provider) => {
|
47 | return provider+SUFFIX_SECRET;
|
48 | }
|
49 |
|
50 | export const getClientSecret = (provider) => {
|
51 | return process.env[getProviderKey(provider)];
|
52 | }
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 | export const createRequestLoginMiddleware = (clientId: string, callbackUrl: string, provider: string, loginUrl: string) => (err, req, res, next) => {
|
63 |
|
64 | const path = require('path');
|
65 |
|
66 | console.log("createRequestLoginMiddleware: ", err);
|
67 |
|
68 | if (provider === AuthenticationProvider.EMAIL) {
|
69 | console.log("request login from: ", loginUrl);
|
70 |
|
71 | const page = err && req.query && req.query.page ? req.query.page : req.url;
|
72 |
|
73 | res.redirect(`${path.join(getBasename(), loginUrl)}?page=${page}${err ? `&message=${err}` : "" }`);
|
74 |
|
75 | } else if (provider === AuthenticationProvider.GITHUB) {
|
76 | res.redirect(`https:
|
77 | } else if (provider === AuthenticationProvider.MEDIUM) {
|
78 | res.redirect(`https://medium.com/m/oauth/authorize?client_id=${clientId}&scope=basicProfile,listPublications,publishPost&state=${req.url}&response_type=code&redirect_uri=${callbackUrl}`);
|
79 | }
|
80 |
|
81 | return;
|
82 |
|
83 | };
|
84 |
|
85 |
|
86 |
|
87 |
|
88 | export const createFetchAccessTokenFunction = (
|
89 | clientId: string,
|
90 | callbackUrl: string,
|
91 | provider: string,
|
92 | senderEmail?: string,
|
93 | getSubject?: (recipient: string) => string,
|
94 | getHtmlText?: (recipient: string, url: string) => string,
|
95 | ) => (req: any) => {
|
96 |
|
97 | if (provider === AuthenticationProvider.EMAIL) {
|
98 |
|
99 | console.log("this is the EMAIL - createFetchAccessTokenFunction middleware");
|
100 |
|
101 | const { email, password, page } = req.query;
|
102 |
|
103 | if (email !== undefined && password !== undefined) {
|
104 |
|
105 |
|
106 | return {
|
107 | redirectPage: page,
|
108 | fFetch: async function () {
|
109 |
|
110 | const uuidv4 = require('uuid/v4');
|
111 | const access_token = uuidv4();
|
112 |
|
113 |
|
114 |
|
115 | const AWS = require('aws-sdk');
|
116 |
|
117 |
|
118 |
|
119 | var params = {
|
120 | Destination: {
|
121 | BccAddresses: [
|
122 | senderEmail
|
123 | ],
|
124 | CcAddresses: [
|
125 | ],
|
126 | ToAddresses: [
|
127 | email
|
128 | ]
|
129 | },
|
130 | Message: {
|
131 | Body: {
|
132 | Html: {
|
133 | Charset: "UTF-8",
|
134 | Data: getHtmlText(email,
|
135 | `${callbackUrl}?${EMAIL_CONFIRMATION_PARAM}=${access_token}&${EMAIL_PARAM}=${email}`)
|
136 | },
|
137 | |
138 |
|
139 |
|
140 |
|
141 | },
|
142 | Subject: {
|
143 | Charset: 'UTF-8',
|
144 | Data: getSubject(email)
|
145 | }
|
146 | },
|
147 | Source: senderEmail,
|
148 | ReplyToAddresses: [
|
149 | senderEmail
|
150 | ],
|
151 | };
|
152 |
|
153 | console.log("this is the fFetch of the mail-authentication");
|
154 |
|
155 | return new Promise(function(resolve, reject) {
|
156 |
|
157 |
|
158 | var sendPromise = new AWS.SES({apiVersion: '2010-12-01'}).sendEmail(params).promise();
|
159 |
|
160 |
|
161 | sendPromise.then(
|
162 | function(data) {
|
163 | console.log(data.MessageId);
|
164 |
|
165 | resolve({
|
166 | id: email,
|
167 | name: "",
|
168 | username: "",
|
169 | imageUrl:"",
|
170 | access_token: access_token,
|
171 | email: email,
|
172 | encrypted_password: password,
|
173 | status: AUTH_STATUS.PENDING
|
174 | })
|
175 | }).catch(
|
176 | function(err) {
|
177 | console.error(err, err.stack);
|
178 | reject(err);
|
179 | });
|
180 |
|
181 |
|
182 | });
|
183 | } |
184 |
|
185 |
|
186 |
|
187 |
|
188 |
|
189 |
|
190 |
|
191 |
|
192 | }
|
193 | }
|
194 |
|
195 |
|
196 |
|
197 | } else if (provider === AuthenticationProvider.GITHUB) {
|
198 |
|
199 |
|
200 |
|
201 |
|
202 |
|
203 |
|
204 |
|
205 | const { state, code, error, page } = req.query;
|
206 |
|
207 |
|
208 | if (error !== undefined) {
|
209 |
|
210 |
|
211 | console.log(error);
|
212 |
|
213 | return;
|
214 |
|
215 | } else if (code !== undefined) {
|
216 |
|
217 | console.log("found redirect page: ", page)
|
218 |
|
219 | return {
|
220 | redirectPage: page,
|
221 | fFetch: async function () {
|
222 | return await fetch('https://github.com/login/oauth/access_token',{
|
223 | method: 'POST',
|
224 | body: `code=${code}&client_id=${clientId}&client_secret=${getClientSecret(provider)}`,
|
225 | headers: {
|
226 | "Content-Type": "application/x-www-form-urlencoded",
|
227 | "Accept": "application/json",
|
228 | "Accept-Charset": "utf-8"
|
229 | }
|
230 | }).then(function(response) {
|
231 | return response.json();
|
232 | })
|
233 | }
|
234 | }
|
235 |
|
236 | };
|
237 |
|
238 |
|
239 | } else if (provider === AuthenticationProvider.MEDIUM) {
|
240 |
|
241 | console.log("Medium callback: ", req.query)
|
242 | const { state, code, error } = req.query;
|
243 |
|
244 |
|
245 | if (error !== undefined) {
|
246 |
|
247 |
|
248 | console.log(error);
|
249 |
|
250 | return;
|
251 |
|
252 | } else if (code !== undefined) {
|
253 | return {
|
254 | redirectPage: state,
|
255 | fFetch: async function () {
|
256 | return await fetch('https://api.medium.com/v1/tokens', {
|
257 | method: 'POST',
|
258 | body: `code=${code}&client_id=${clientId}&client_secret=${getClientSecret(provider)}&grant_type=authorization_code&redirect_uri=${callbackUrl}`,
|
259 | headers: {
|
260 | "Content-Type": "application/x-www-form-urlencoded",
|
261 | "Accept": "application/json",
|
262 | "Accept-Charset": "utf-8"
|
263 | }
|
264 | }).then(function (response) {
|
265 | console.log("request data: ", response)
|
266 | return response.json();
|
267 | })
|
268 | }
|
269 | }
|
270 | }
|
271 |
|
272 | }
|
273 |
|
274 |
|
275 |
|
276 |
|
277 |
|
278 | };
|
279 |
|
280 | export const createGetUserFunction = (provider: string) => async function (resJson: any): Promise<IUserData> {
|
281 |
|
282 | console.log("createGetUserFunction: ", resJson);
|
283 |
|
284 | if (provider === AuthenticationProvider.EMAIL) {
|
285 |
|
286 |
|
287 | return new Promise(function(resolve, reject) {
|
288 |
|
289 | resolve(resJson)
|
290 | });
|
291 |
|
292 | } else if (provider === AuthenticationProvider.GITHUB) {
|
293 | const { token_type, access_token, } = resJson;
|
294 |
|
295 |
|
296 | return await fetch('https://api.github.com/user',{
|
297 | method: 'GET',
|
298 | headers: {
|
299 | "Content-Type": "application/json",
|
300 | "Accept": "application/json",
|
301 | "Accept-Charset": "utf-8",
|
302 | "Authorization": token_type+" "+access_token
|
303 | }
|
304 | }).then(function(response) {
|
305 |
|
306 |
|
307 | return response.json();
|
308 |
|
309 | }).then(function(data){
|
310 |
|
311 | |
312 |
|
313 |
|
314 |
|
315 |
|
316 |
|
317 |
|
318 |
|
319 |
|
320 |
|
321 |
|
322 |
|
323 |
|
324 |
|
325 |
|
326 |
|
327 |
|
328 |
|
329 |
|
330 |
|
331 |
|
332 |
|
333 |
|
334 |
|
335 |
|
336 |
|
337 |
|
338 |
|
339 |
|
340 |
|
341 |
|
342 |
|
343 |
|
344 |
|
345 | return {
|
346 | id: data.id,
|
347 | name: data.name,
|
348 | username: data.login,
|
349 | imageUrl: data.avatar_url,
|
350 | email: data.email,
|
351 | access_token: access_token,
|
352 | status: AUTH_STATUS.ACTIVE
|
353 | }
|
354 |
|
355 | });
|
356 |
|
357 |
|
358 |
|
359 | } else if (provider === AuthenticationProvider.MEDIUM) {
|
360 | console.log("fetch user data of medium-user")
|
361 | const { token_type, access_token, refresh_token, scope, expires_at } = resJson;
|
362 |
|
363 |
|
364 | return await fetch('https://api.medium.com/v1/me',{
|
365 | method: 'GET',
|
366 | headers: {
|
367 | "Content-Type": "application/json",
|
368 | "Accept": "application/json",
|
369 | "Accept-Charset": "utf-8",
|
370 | "Authorization": token_type+" "+access_token
|
371 | }
|
372 | }).then(function(response) {
|
373 |
|
374 |
|
375 |
|
376 | return response.json();
|
377 |
|
378 | }).then(function({data}){
|
379 |
|
380 | console.log("user data parsed: ", data);
|
381 | return {
|
382 | id: data.id,
|
383 | name: data.name,
|
384 | username: data.username,
|
385 | imageUrl: data.imageUrl,
|
386 | email: data.email,
|
387 | access_token: access_token,
|
388 | status: AUTH_STATUS.ACTIVE
|
389 | }
|
390 |
|
391 | });
|
392 |
|
393 |
|
394 | }
|
395 |
|
396 |
|
397 |
|
398 |
|
399 | }
|
400 |
|
401 |
|
402 |
|
403 |
|
404 | const SUFFIX_SECRET = "_SECRET"
|
405 |
|
406 |
|
407 |
|
408 |
|
409 | export interface IAuthenticationArgs {
|
410 |
|
411 | |
412 |
|
413 |
|
414 | id: string,
|
415 |
|
416 | |
417 |
|
418 |
|
419 | provider: string,
|
420 |
|
421 | |
422 |
|
423 |
|
424 | clientId?: string,
|
425 |
|
426 | |
427 |
|
428 |
|
429 | callbackUrl: string,
|
430 |
|
431 |
|
432 | |
433 |
|
434 |
|
435 | loginUrl: string,
|
436 |
|
437 | |
438 |
|
439 |
|
440 |
|
441 | senderEmail?: string,
|
442 |
|
443 | |
444 |
|
445 |
|
446 |
|
447 | getSubject: (recipient: string) => string,
|
448 |
|
449 | |
450 |
|
451 |
|
452 |
|
453 |
|
454 | getHtmlText: (recipient: string, url: string) => string,
|
455 |
|
456 | }
|
457 |
|
458 |
|
459 |
|
460 |
|
461 |
|
462 | export interface IAuthenticationProps {
|
463 |
|
464 | setStoreIdentityData: (
|
465 | storeIdentityData: (request: any, key: string, val: any, jsonData: any) => void
|
466 | ) => void,
|
467 |
|
468 | storeAuthData?: (request: any, key: string, val: any, jsonData: any) => void,
|
469 |
|
470 | setGetIdentityData: (getIdentityData: (request: any, matchBrowserIdentity: boolean, key: string, val: any) => any) => void,
|
471 |
|
472 | getAuthData?: (request: any, matchBrowserIdentity: boolean, key: string, val: any) => any,
|
473 |
|
474 | |
475 |
|
476 |
|
477 |
|
478 | authCallback: (email: string, password: string, page: string, onResponse: (code:string)=> void) => void
|
479 | }
|
480 |
|
481 |
|
482 |
|
483 |
|
484 |
|
485 |
|
486 |
|
487 | export default (props: IAuthenticationArgs | any) => {
|
488 |
|
489 |
|
490 |
|
491 | const componentProps: IInfrastructure & IComponent = {
|
492 | infrastructureType: Types.INFRASTRUCTURE_TYPE_COMPONENT,
|
493 | instanceType: AUTHENTICATION_INSTANCE_TYPE,
|
494 | instanceId: props.id,
|
495 | };
|
496 |
|
497 |
|
498 |
|
499 | const authenticationProps: IAuthenticationProps = {
|
500 |
|
501 |
|
502 | setStoreIdentityData: (storeIdentityData: (request: any, key: string, val: any, jsonData: any) => void) => {
|
503 |
|
504 | props.storeAuthData = storeIdentityData;
|
505 |
|
506 |
|
507 | },
|
508 |
|
509 | setGetIdentityData: (getIdentityData: (request: any, matchBrowserIdentity: boolean, key: string, val: any) => any ) => {
|
510 | props.getAuthData = getIdentityData;
|
511 | },
|
512 |
|
513 | authCallback: (email: string, password: string, page: string, onResponse: (code:string) => void) => {
|
514 | console.log("this is the auth-Callback");
|
515 |
|
516 | if (props.provider === AuthenticationProvider.EMAIL) {
|
517 |
|
518 | if (!(/^([A-Za-z0-9_\-.])+@([A-Za-z0-9_\-.])+\.([A-Za-z]{2,})$/.test(email))) {
|
519 | onResponse(AUTH_RESPONSE.EMAIL_INVALID);
|
520 | return;
|
521 | }
|
522 |
|
523 |
|
524 |
|
525 | window.location.href = `${props.callbackUrl}?${EMAIL_PARAM}=${email}&${PASSWORD_PARAM}=${password}&page=${page}`;
|
526 | return;
|
527 | }
|
528 |
|
529 |
|
530 | onResponse(AUTH_RESPONSE.NOT_IMPLEMENTED);
|
531 |
|
532 | }
|
533 |
|
534 | };
|
535 |
|
536 | |
537 |
|
538 |
|
539 |
|
540 | const onAuthenticated = (userid:string): void => {
|
541 | console.log("just authenticated, tell the securedEntries about it")
|
542 |
|
543 | findComponentRecursively(props.children, (c) => c.setUserId !== undefined).forEach( se => {
|
544 |
|
545 | se.setUserId(userid)
|
546 |
|
547 | });
|
548 | };
|
549 |
|
550 | findComponentRecursively(props.children, (c) => c.setMiddleware !== undefined).forEach( se => {
|
551 |
|
552 | se.setMiddleware(createMiddleware({ callback:createAuthMiddleware(getClientSecret(props.provider), onAuthenticated)}))
|
553 |
|
554 | });
|
555 |
|
556 |
|
557 | findComponentRecursively(props.children, isSecuredRoute).forEach( sr => {
|
558 |
|
559 |
|
560 | sr.middlewares = [
|
561 |
|
562 | createMiddleware({ callback: createAuthMiddleware(getClientSecret(props.provider), onAuthenticated)}),
|
563 |
|
564 |
|
565 | createMiddleware({ callback: createRequestLoginMiddleware(props.clientId, props.callbackUrl, props.provider, props.loginUrl)})
|
566 |
|
567 | ].concat(sr.middlewares);
|
568 |
|
569 |
|
570 | sr.instanceType = ROUTE_INSTANCE_TYPE;
|
571 |
|
572 | });
|
573 |
|
574 |
|
575 | findComponentRecursively(props.children, isSecuredService).forEach( service => {
|
576 |
|
577 |
|
578 | service.middlewares = [
|
579 |
|
580 | createMiddleware({ callback: createAuthMiddleware(getClientSecret(props.provider), onAuthenticated)}),
|
581 |
|
582 |
|
583 | createMiddleware({ callback: createRequestLoginMiddleware(props.clientId, props.callbackUrl, props.provider, props.loginUrl)})
|
584 |
|
585 | ].concat(service.middlewares);
|
586 |
|
587 |
|
588 | service.instanceType = SERVICE_INSTANCE_TYPE;
|
589 |
|
590 | });
|
591 |
|
592 |
|
593 |
|
594 |
|
595 | findComponentRecursively(props.children, (child) => child.setAuthenticationId !== undefined).forEach( child => {
|
596 | child.setAuthenticationId(props.id)
|
597 | });
|
598 |
|
599 | |
600 |
|
601 |
|
602 | const mappedChildren = {
|
603 |
|
604 | children: [
|
605 |
|
606 |
|
607 | createWebApp({
|
608 | id: "WEBAPP_"+props.provider,
|
609 | path: props.callbackUrl.substring(props.callbackUrl.lastIndexOf("/")),
|
610 | method: "GET",
|
611 | children: [
|
612 |
|
613 |
|
614 | createMiddleware({ callback: bodyParser.json() }),
|
615 | createMiddleware({ callback: bodyParser.urlencoded({
|
616 | extended: true
|
617 | }) }),
|
618 |
|
619 | createMiddleware({ callback: createCallbackMiddleware(
|
620 | getClientSecret(props.provider),
|
621 | createFetchAccessTokenFunction(
|
622 | props.clientId,
|
623 | props.callbackUrl,
|
624 | props.provider,
|
625 | props.senderEmail,
|
626 | props.getSubject,
|
627 | props.getHtmlText
|
628 |
|
629 | ),
|
630 | createGetUserFunction(props.provider),
|
631 | async function (request: any, key: string, val: any, jsonData: any) {
|
632 | return await props.storeAuthData(request, key, val, jsonData)
|
633 | },
|
634 | async function (request: any, matchBrowserIdentity: boolean, key: string, val: any) {
|
635 | return await props.getAuthData(request, matchBrowserIdentity, key, val)
|
636 | }
|
637 |
|
638 | )}),
|
639 |
|
640 | createMiddleware({
|
641 | callback: createRequestLoginMiddleware(props.clientId, props.callbackUrl, props.provider, props.loginUrl)
|
642 | }),
|
643 |
|
644 |
|
645 |
|
646 | createRoute({
|
647 | path: props.callbackUrl.substring(props.callbackUrl.lastIndexOf("/")),
|
648 | name: "WEBAPP_"+props.provider,
|
649 | render: (rp) => <div>This is the callback route!</div>,
|
650 | })
|
651 |
|
652 | ]
|
653 | })
|
654 |
|
655 |
|
656 |
|
657 |
|
658 | ].concat(getChildrenArray(props.children))
|
659 | };
|
660 |
|
661 | console.log("authentication: ", findComponentRecursively(props.children, isWebApp));
|
662 | return Object.assign(props, componentProps, authenticationProps, mappedChildren);
|
663 |
|
664 | };
|
665 |
|
666 | export const isAuthentication = (component) => {
|
667 |
|
668 | return component !== undefined &&
|
669 | component.instanceType === AUTHENTICATION_INSTANCE_TYPE;
|
670 | }; |
\ | No newline at end of file |