UNPKG

ws2801-webserver

Version:

A ready-to-use webserver for the WS2801-Pi package.

260 lines (188 loc) 7.15 kB
import express from 'express'; import ip from 'ip'; import {Logger} from './logger'; import {Persister} from './persister'; import {Webserver} from './webserver'; import {Config, User} from './types/index'; type ResolveFunctionForRegistrationConfirmation = {name: string, resolveFunction: Function}; export class AuthService { private persister: Persister; private webserver: Webserver; private confirmationWebserver: Webserver; private logger: Logger; private config: Config; private users: Array<User>; private resolveFunctionsForRegistrationConfirmation: Array<ResolveFunctionForRegistrationConfirmation> = []; constructor(config: Config, webserver: Webserver) { this.config = config; this.webserver = webserver; this.logger = new Logger('Auth Service'); this.persister = new Persister(); this.confirmationWebserver = new Webserver(this.config.confirmationPort, this.config.logRequests === true); } public start(): void { this.loadUsers(); this.addAuthMiddleware(); this.addAuthRoutes(); this.confirmationWebserver.start(); } public stop(): void { this.confirmationWebserver.stop(); } private registerUser(name: string, apiKey: string): User { const userExists: boolean = this.users.some((user: User): boolean => user.name === name); if (userExists) { throw new Error(`User with name '${name}' already exists.`); } const newUser: User = this.generateUser(name, apiKey); this.users.push(newUser); this.saveUsers(); return newUser; } // private setUserIsAllowed(name: string, allowed: boolean): void { // this.users.find((user: User): boolean => user.name === name).allowed = allowed; // } private getUserByName(name: string): User { return this.users.find((user: User): boolean => user.name === name); } private getUserByApiKey(apiKey: string): User { return this.users.find((user: User): boolean => user.apiKey === apiKey); } private generateUser(name: string, apiKey: string): User { const user: User = { name: name, allowed: true, apiKey: apiKey, }; return user; } private loadUsers(): void { const usersAsString: string = this.persister.loadData('webserver-api-keys.json'); if (!usersAsString) { this.users = []; this.saveUsers(); return; } this.users = JSON.parse(usersAsString); } private saveUsers(): void { this.persister.saveData('webserver-api-keys.json', JSON.stringify(this.users, null, 2)); } private addAuthMiddleware(): void { this.webserver.addMiddleware(this.authMiddleware.bind(this)); } private addAuthRoutes(): void { this.webserver.addPostRoute('/register', this.register.bind(this)); this.webserver.addPostRoute('/login', this.login.bind(this)); this.confirmationWebserver.addGetRoute('/confirm-registration', this.confirmRegistration.bind(this)); } private async waitForConfirmation(name: string): Promise<void> { const confirmationForUserExists: boolean = this.resolveFunctionsForRegistrationConfirmation .some((resolveFunction: ResolveFunctionForRegistrationConfirmation): boolean => { return resolveFunction.name === name; }); if (confirmationForUserExists) { throw new Error(`The user "${name}" is already waiting for confirmation.`); } const resolvePromise: Promise<void> = new Promise((resolve: Function): void => { this.resolveFunctionsForRegistrationConfirmation.push({ name: name, resolveFunction: resolve, }); this.logger.log(`User '${name}' would like to register. Click this link to confirm: http://${ip.address()}:${this.config.confirmationPort}/confirm-registration?name=${name.replace(/ /g, '%20').replace(/ /g, '%C2%A0')}`); }); return resolvePromise; } // Middleware functions private authMiddleware(request: express.Request, response: express.Response, next: Function): void { if (request.path === '/login-required' || request.path === '/register' || request.path === '/login') { next(); return; } const apiKey: string = request.query.apiKey as string; if (!apiKey) { response.status(401).send('Please register first.'); return; } const user: User = this.getUserByApiKey(apiKey); if (!user) { response.status(401).send('Please register first.'); return; } if (!user.allowed) { response.status(403).send(`You are not allowed to do that.`); return; } next(); } // Route functions private async register(request: express.Request, response: express.Response): Promise<void> { const name: string = request.body.name; const apiKey: string = request.body.apiKey; if (!name) { response.status(400).send(`Request body must contain a 'name'.`); return; } if (!apiKey) { response.status(400).send(`Request body must contain a 'apiKey'.`); return; } const userAlreadyExists: boolean = this.getUserByName(name) !== undefined; if (userAlreadyExists) { response.status(403).send(`User '${name}' already exists.`); return; } try { await this.waitForConfirmation(name); } catch (error) { response.status(403).send(error.message); return; } try { this.registerUser(name, apiKey); response.status(200).json({apiKey: apiKey}); } catch (error) { response.status(403).send(error.message); } } private confirmRegistration(request: express.Request, response: express.Response): void { const nameInQuery: string = request.query.name as string; if (!nameInQuery) { response.status(400).send(`Request must contain name as query param.`); return; } const resolveFunctionIndex: number = this.resolveFunctionsForRegistrationConfirmation .findIndex((resolveFunctionWithName: ResolveFunctionForRegistrationConfirmation): boolean => { return resolveFunctionWithName.name === nameInQuery; }); if (resolveFunctionIndex === -1) { response.status(400).send(`Could not confirm registration.`); return; } this.resolveFunctionsForRegistrationConfirmation[resolveFunctionIndex].resolveFunction(); this.resolveFunctionsForRegistrationConfirmation.splice(resolveFunctionIndex); response.status(200).send(`User '${nameInQuery}' was successfully registered.`); } private async login(request: express.Request, response: express.Response): Promise<void> { const apiKey: string = request.query.apiKey as string; if (!apiKey) { response.status(400).send(`Request must contain 'apiKey' as query parameter.`); return; } try { const user: User = this.getUserByApiKey(apiKey); if (!user) { throw new Error('User is not registered. Try to register again.'); } if (!user.allowed) { throw new Error('You are not allowed to login.'); } response.status(200).json({loggedIn: true}); } catch (error) { response.status(403).send(error.message); } } }