Skip to content
Snippets Groups Projects
Select Git revision
  • 7eeb8014935ecc206242c8783bde94f35fc522b6
  • main default protected
2 results

index.html

Blame
  • SessionManager.ts 12.24 KiB
    import * as jwt                  from 'jsonwebtoken';
    import User                      from '../sharedByClients/models/User';
    import LocalConfigKeys           from '../types/LocalConfigKeys';
    import axios, { HttpStatusCode } from 'axios';
    import HttpManager               from './HttpManager';
    import ora                       from 'ora';
    import Permissions               from '../types/Permissions';
    import ApiRoute                  from '../sharedByClients/types/Dojo/ApiRoute';
    import DojoBackendManager        from './DojoBackendManager';
    import Config                    from '../config/Config';
    import ClientsSharedConfig       from '../sharedByClients/config/ClientsSharedConfig';
    import DojoGitlabCredentials     from '../sharedByClients/types/Dojo/DojoGitlabCredentials';
    import * as http                 from 'http';
    import EventEmitter              from 'events';
    import SharedConfig              from '../shared/config/SharedConfig';
    import chalk                     from 'chalk';
    import inquirer                  from 'inquirer';
    import SharedGitlabManager       from '../shared/managers/SharedGitlabManager';
    import GitlabManager             from './GitlabManager';
    import GitlabToken               from '../shared/types/Gitlab/GitlabToken';
    import open                      from 'open';
    import { sessionConfigFile }     from '../config/ConfigFiles';
    
    
    class LoginServer {
        readonly events: EventEmitter = new EventEmitter();
        private server: http.Server;
    
        constructor() {
            this.server = http.createServer((req, res) => {
                const sendError = (error: string) => {
                    this.events.emit('error', error);
                    res.writeHead(HttpStatusCode.InternalServerError, { 'Content-Type': 'text/html' });
                    res.write(`<html lang="en"><body><h1 style="color: red">DojoCLI login error</h1><h3>Please look at your CLI for more informations.</h3></body></html>`);
                    res.end();
                };
    
                if ( req.url?.match(Config.login.server.route) ) {
                    const urlParts = req.url.split('=');
                    if ( urlParts.length > 0 ) {
                        this.events.emit('code', urlParts[1]);
    
                        res.writeHead(HttpStatusCode.Ok, { 'Content-Type': 'text/html' });
                        res.write(`<html lang="en"><body><h1 style="color: green">DojoCLI login successful</h1><h3>You can close this window.</h3></body></html>`);
                        res.end();
                        return;
                    }
    
                    sendError(`Incorrect call => ${ req.url }`);
                    return;
                }
    
                //sendError(`Unknown route call => ${ req.url }`);
            });
        }
    
        start() {
            try {
                this.server.listen(Config.login.server.port);
                this.events.emit('started');
            } catch ( error ) {
                this.events.emit('error', error);
            }
        }
    
        stop() {
            try {
                this.server.close();
                this.events.emit('stopped');
            } catch ( error ) {
                this.events.emit('error', error);
            }
        }
    }
    
    
    class SessionManager {
        public profile: User | undefined = undefined;
    
        get isLogged(): boolean {
            return this.apiToken !== null && this.apiToken !== '';
        }
    
        get apiToken(): string {
            const apisToken = sessionConfigFile.getParam(LocalConfigKeys.APIS_TOKEN) as null | { [key: string]: string };
    
            if ( apisToken !== null && ClientsSharedConfig.apiURL in apisToken ) {
                return apisToken[ClientsSharedConfig.apiURL];
            }
    
            return '';
        }
    
        set apiToken(token: string) {
            let apisToken = sessionConfigFile.getParam(LocalConfigKeys.APIS_TOKEN) as null | { [key: string]: string };
            if ( apisToken === null ) {
                apisToken = {};
            }
            apisToken[ClientsSharedConfig.apiURL] = token;
            sessionConfigFile.setParam(LocalConfigKeys.APIS_TOKEN, apisToken);
    
            try {
                const payload = jwt.decode(token);
    
                if ( payload && typeof payload === 'object' && payload.profile ) {
                    this.profile = payload.profile as User;
                }
            } catch ( error ) {
                this.profile = undefined;
            }
        }
    
        get gitlabCredentials(): DojoGitlabCredentials {
            return sessionConfigFile.getParam(LocalConfigKeys.GITLAB) as DojoGitlabCredentials;
        }
    
        set gitlabCredentials(credentials: DojoGitlabCredentials) {
            sessionConfigFile.setParam(LocalConfigKeys.GITLAB, credentials);
        }
    
        constructor() { }
    
        private async getGitlabCodeFromHeadlessEnvironment(): Promise<string> {
            const indent: string = '    ';
            console.log(`${ indent }Please open the following URL in your web browser and accept to give the requested permissions to Dojo:`);
            console.log(chalk.blue(`${ indent }${ Config.login.gitlab.url.code }`));
            console.log(`${ indent }Then, copy the code at the end of the redirected url and paste it bellow.`);
            console.log(`${ indent }Example of url (here the code is 123456): ${ chalk.blue(`${ SharedConfig.login.gitlab.url.redirect }?code=`) }${ chalk.green('123456') }`);
            return (await inquirer.prompt({
                                              type   : 'password',
                                              name   : 'code',
                                              message: `${ chalk.green('?') } Please paste the Gitlab code here`,
                                              mask   : '*',
                                              prefix : '   '
                                          })).code;
        }
    
        private getGitlabCodeFromGraphicEnvironment(): Promise<string> {
            return new Promise<string>((resolve, reject) => {
                ora({
                        text  : 'GUI mode (for headless mode, use the --cli option)',
                        indent: 4
                    }).start().info();
    
                let currentSpinner: ora.Ora = ora({
                                                      text  : 'Starting login server',
                                                      indent: 4
                                                  }).start();
    
                const loginServer = new LoginServer();
                loginServer.events.on('started', () => {
                    currentSpinner.succeed('Login server started');
                    currentSpinner = ora({
                                             text  : `Waiting for user to authorize the application in his web browser. If the browser does not open automatically, please go to : ${ Config.login.gitlab.url.code }`,
                                             indent: 4
                                         }).start();
                    open(Config.login.gitlab.url.code).then();
                });
                loginServer.events.on('error', (error: string) => {
                    currentSpinner.fail(`Login server error: ${ error }`);
                    reject();
                });
                loginServer.events.on('stopped', () => {
                    currentSpinner.succeed('Login server stopped');
                });
    
    
                loginServer.events.on('code', (code: string) => {
                    currentSpinner.succeed('Login code received');
                    currentSpinner = ora({
                                             text  : 'Stopping login server',
                                             indent: 4
                                         }).start();
                    loginServer.events.on('stopped', () => {
                        resolve(code);
                    });
                    loginServer.stop();
    
                    resolve(code);
                });
    
                loginServer.start();
            });
        }
    
        async login(headless: boolean = false) {
            try {
                this.logout();
            } catch ( error ) {
                console.log(error);
                ora('Unknown error').start().fail();
                throw error;
            }
    
            ora(`Login with Gitlab (${ SharedConfig.gitlab.URL }):`).start().info();
    
            let gitlabCode: string;
            if ( !headless ) {
                gitlabCode = await this.getGitlabCodeFromGraphicEnvironment();
            } else {
                gitlabCode = await this.getGitlabCodeFromHeadlessEnvironment();
            }
    
            const gitlabTokensSpinner = ora({
                                                text  : 'Retrieving gitlab tokens',
                                                indent: 4
                                            }).start();
            let gitlabTokens: GitlabToken;
            try {
                gitlabTokens = await SharedGitlabManager.getTokens(gitlabCode);
                this.gitlabCredentials = {
                    refreshToken: gitlabTokens.refresh_token,
                    accessToken : gitlabTokens.access_token
                };
                gitlabTokensSpinner.succeed('Gitlab tokens retrieved');
            } catch ( error ) {
                gitlabTokensSpinner.fail('Error while retrieving gitlab tokens');
                throw error;
            }
    
            const isGitlabTokensValid = (await GitlabManager.testToken()).every((value) => value);
            if ( !isGitlabTokensValid ) {
                throw new Error('Gitlab tokens are invalid');
            }
    
            ora(`Login to Dojo backend:`).start().info();
            const dojoLoginSpinner = ora({
                                             text  : 'Login to Dojo backend',
                                             indent: 4
                                         }).start();
    
            try {
                await DojoBackendManager.login(gitlabTokens);
                dojoLoginSpinner.succeed('Logged in');
            } catch ( error ) {
                dojoLoginSpinner.fail('Login failed');
                throw error;
            }
    
            await this.testSession(true);
        }
    
        async refreshTokens() {
            const gitlabTokens = await DojoBackendManager.refreshTokens(this.gitlabCredentials.refreshToken!);
    
            this.gitlabCredentials = {
                refreshToken: gitlabTokens.refresh_token,
                accessToken : gitlabTokens.access_token
            };
        }
    
        logout() {
            this.apiToken = '';
            this.gitlabCredentials = {
                refreshToken: '',
                accessToken : ''
            };
        }
    
        checkPermissions(verbose: boolean = true, indent: number = 8, checkPermissions: Array<string> | null = []): Permissions {
            const hasPermission = (permissionPredicate: () => boolean, verboseText: string): boolean => {
                const isAllowed: boolean = this.profile !== undefined && permissionPredicate();
    
                if ( verbose ) {
                    const spinner: ora.Ora = ora({
                                                     text  : verboseText,
                                                     indent: indent
                                                 }).start();
                    isAllowed ? spinner.succeed() : spinner.fail();
                }
    
                return isAllowed;
            };
    
            return {
                student      : checkPermissions && (checkPermissions.length == 0 || checkPermissions.includes('student')) ? hasPermission(() => true, 'Student permissions') : false,
                teachingStaff: checkPermissions && (checkPermissions.length == 0 || checkPermissions.includes('teachingStaff')) ? hasPermission(() => this.profile?.isTeachingStaff ?? false, 'Teaching staff permissions') : false,
                admin        : checkPermissions && (checkPermissions.length == 0 || checkPermissions.includes('admin')) ? hasPermission(() => this.profile?.isAdmin ?? false, 'Admin permissions') : false
            };
        }
    
    
        async testSession(verbose: boolean = true, checkPermissions: Array<string> | null = []): Promise<false | Permissions> {
            if ( verbose ) {
                ora('Checking Dojo session: ').start().info();
            }
    
            HttpManager.handleAuthorizationCommandErrors = false;
    
            const spinner: ora.Ora = ora({
                                             text  : `Testing Dojo session`,
                                             indent: 4
                                         });
            if ( verbose ) {
                spinner.start();
            }
    
            try {
                await axios.get(DojoBackendManager.getApiUrl(ApiRoute.TEST_SESSION), {});
    
                if ( verbose ) {
                    spinner.succeed(`The session is valid`);
                }
            } catch ( error ) {
                if ( verbose ) {
                    spinner.fail(`The session is invalid`);
                }
    
                return false;
            }
    
            return this.checkPermissions(verbose, 8, checkPermissions);
        }
    }
    
    
    export default new SessionManager();