OpenHPS/openhps-solid

View on GitHub
src/node/SolidClientService.ts

Summary

Maintainability
B
7 hrs
Test Coverage
import express from 'express';
const cookieSession = require('cookie-session'); // eslint-disable-line
import { Session, ISessionInfo, ISessionOptions } from '@inrupt/solid-client-authn-node';
import { SolidDataServiceOptions, SolidService, SolidSession } from '../common/SolidService';
import { SolidProfileObject } from '../common';
import { interactiveLogin } from 'solid-node-interactive-auth';

/**
 * Solid client service
 */
export class SolidClientService extends SolidService {
    protected options: SolidDataClientOptions;
    protected express: express.Express;

    constructor(options?: SolidDataClientOptions) {
        super(options);
        if (this.options.loginSuccessCallback)
            this.options.loginSuccessCallback = this.options.loginSuccessCallback.bind(this);
        if (this.options.loginErrorCallback)
            this.options.loginErrorCallback = this.options.loginErrorCallback.bind(this);
        this.once('build', this._onBuild.bind(this));
    }

    private _onBuild(): Promise<void> {
        return new Promise((resolve, reject) => {
            if (this.options.authServer) {
                if (!Object.keys(this.options.authServer).includes('port')) {
                    this.express = this.options.authServer as express.Express;
                } else {
                    const authOptions = this.options.authServer as SolidAuthServerOptions;
                    this.express = express();
                    this.express.use(
                        cookieSession({
                            name: 'session',
                            keys: authOptions.cookies ? authOptions.cookies.keys : ['test', 'test2'],
                            maxAge: authOptions.cookies ? authOptions.cookies.maxAge : 24 * 60 * 60 * 1000,
                        }),
                    );
                    this.express.listen(authOptions.port, () => undefined);
                }
                this.express.get(this.options.loginPath, this.onLogin.bind(this));
                this.express.get(this.options.redirectPath, this.onRedirect.bind(this));
            }

            if (this.options.autoLogin) {
                this.login(this.options.defaultOidcIssuer)
                    .then(() => resolve())
                    .catch(reject);
            } else {
                resolve();
            }
        });
    }

    logout(session: SolidSession): Promise<void> {
        return new Promise((resolve, reject) => {
            session
                .logout()
                .then(() => {
                    this.session = undefined;
                    return this.storage.delete('currentSession');
                })
                .then(() => {
                    resolve();
                })
                .catch(reject);
        });
    }

    /**
     * Login a Solid user
     * @param {string} oidcIssuer OpenID Issuer
     * @param interactive
     * @returns {Promise<Session>} Session promise
     */
    login(oidcIssuer: string = this.options.defaultOidcIssuer, interactive: boolean = false): Promise<Session> {
        const session = this.createSession({
            storage: this.storage,
        });
        if (!interactive) {
            return new Promise((resolve, reject) => {
                session
                    .login({
                        oidcIssuer,
                        clientId: this.options.clientId,
                        clientSecret: this.options.clientSecret,
                        clientName: this.options.clientName,
                    })
                    .then(async () => {
                        this.session = session;
                        await this.storage.set('currentSession', session.info.sessionId);
                        return this.storage.get(`solidClientAuthenticationUser:${session.info.sessionId}`);
                    })
                    .then((data) => {
                        const sessionData = JSON.parse(data);
                        sessionData.webId = session.info.webId;
                        sessionData.issuer = oidcIssuer;
                        return this.storage.set(
                            `solidClientAuthenticationUser:${session.info.sessionId}`,
                            JSON.stringify(sessionData),
                        );
                    })
                    .then(() => {
                        const object = new SolidProfileObject(this.session.info.webId);
                        object.sessionId = this.session.info.sessionId;
                        return Promise.all([this.session.info, this.storeProfile(object)]);
                    })
                    .then(() => {
                        resolve(session);
                    })
                    .catch(reject);
            });
        } else {
            return interactiveLogin({
                session,
                oidcIssuer,
                clientId: this.options.clientId,
                clientSecret: this.options.clientSecret,
                clientName: this.options.clientName,
            });
        }
    }

    protected onLogin(req: express.Request, res: express.Response): void {
        const session = this.createSession({
            storage: this.storage,
        });
        req.session!.sessionId = session.info.sessionId;
        session
            .login({
                redirectUrl: this.options.redirectUrl,
                oidcIssuer: this.options.defaultOidcIssuer,
                clientName: this.options.clientName,
                handleRedirect: (redirectUrl) => res.redirect(redirectUrl),
            })
            .catch((reason) => {
                this.options.loginErrorCallback(req, res, session.info, reason);
            });
    }

    protected onRedirect(req: express.Request, res: express.Response): void {
        this.findSessionById(req.session!.sessionId)
            .then((session) => {
                return session.handleIncomingRedirect(req.protocol + '://' + req.get('host') + req.originalUrl);
            })
            .then((info) => {
                const object = new SolidProfileObject(info.webId);
                object.sessionId = info.sessionId;
                return Promise.all([info, this.storeProfile(object)]);
            })
            .then(([info]) => {
                if (info.isLoggedIn) {
                    this.options.loginSuccessCallback(req, res, info);
                } else {
                    this.options.loginErrorCallback(req, res, info, 'Not logged in after redirect');
                }
            })
            .catch((reason) => {
                this.options.loginErrorCallback(req, res, req.session!.sessionId, reason);
            });
    }

    protected createSession(options: Partial<ISessionOptions>): Session {
        return new Session(options);
    }
}

export interface SolidDataClientOptions extends SolidDataServiceOptions {
    loginPath?: string;
    redirectPath?: string;
    redirectUrl?: string;
    authServer?: SolidAuthServerOptions | express.Express;
    loginSuccessCallback?: (req: express.Request, res: express.Response, sessionInfo: ISessionInfo) => void;
    loginErrorCallback?: (req: express.Request, res: express.Response, sessionInfo: ISessionInfo, reason: any) => void;
}

export interface SolidAuthServerOptions {
    port: number;
    cookies?: {
        keys: string[];
        maxAge: number;
    };
}