• [Node] Stateful Session Management for login, logout and signup


    Stateful session management: Store session which associate with user, and store in the menory on server.

    Sign Up:

    app.route('/api/signup')
      .post(createUser);
    import {Request, Response} from 'express';
    import {db} from './database';
    
    import * as argon2 from 'argon2';
    import {validatePassword} from './password-validation';
    import {randomBytes} from './security.utils';
    import {sessionStore} from './session-store';
    
    export function createUser (req: Request, res: Response) {
      const credentials = req.body;
    
      const errors = validatePassword(credentials.password);
    
      if (errors.length > 0) {
        res.status(400).json({
          errors
        });
      } else {
        createUserAndSession(res, credentials);
      }
    }
    
    async function createUserAndSession(res, credentials) {
      // Create a password digest
      const passwordDigest = await argon2.hash(credentials.password);
      // Save into db
      const user = db.createUser(credentials.email, passwordDigest);
      // create random session id
      const sessionId = await randomBytes(32).then(bytes => bytes.toString('hex'));
      // link sessionId with user
      sessionStore.createSession(sessionId, user);
      // set sessionid into cookie
      res.cookie('SESSIONID', sessionId, {
        httpOnly: true, // js cannot access cookie
        secure: true // enable https only
      });
      // send back to UI
      res.status(200).json({id: user.id, email: user.email});
    }

    Password validation:

    import * as passwordValidator from 'password-validator';
    
    // Create a schema
    const schema = new passwordValidator();
    
    // Add properties to it
    schema
      .is().min(7)                                    // Minimum length 7
      .has().uppercase()                              // Must have uppercase letters
      .has().lowercase()                              // Must have lowercase letters
      .has().digits()                                 // Must have digits
      .has().not().spaces()                           // Should not have spaces
      .is().not().oneOf(['Passw0rd', 'Password123']); // Blacklist these values
    
    export function validatePassword(password: string) {
      return schema.validate(password, {list: true});
    }

    Random bytes generator:

    const util = require('util');
    const crypto = require('crypto');
    
    // convert a callback based code to promise based
    export const randomBytes = util.promisify(
      crypto.randomBytes
    );

    Session storage:

    import {Session} from './session';
    import {User} from '../src/app/model/user';
    class SessionStore {
      private sessions: {[key: string]: Session} = {};
    
      createSession(sessionId: string, user: User) {
        this.sessions[sessionId] = new Session(sessionId, user);
      }
    
      findUserBySessionId(sessionId: string): User | undefined {
        const session = this.sessions[sessionId];
        return this.isSessionValid(sessionId) ? session.user : undefined;
      }
    
      isSessionValid(sessionId: string): boolean {
        const session = this.sessions[sessionId];
        return session && session.isValid();
      }
    
      destroySession(sessionId: string): void {
        delete this.sessions[sessionId];
      }
    }
    
    // We want only global singleton
    export const sessionStore = new SessionStore();

    In menory database:

    import * as _ from 'lodash';
    import {LESSONS, USERS} from './database-data';
    import {DbUser} from './db-user';
    
    
    class InMemoryDatabase {
    
      userCounter = 0;
    
        createUser(email, passwordDigest) {
          const id = ++this.userCounter;
          const user: DbUser = {
            id,
            email,
            passwordDigest
          };
    
          USERS[id] = user;
    
          return user;
        }
    
      findUserByEmail(email: string): DbUser {
        const users = _.values(USERS);
        return _.find(users, user => user.email === email);
      }
    }
    
    export const db = new InMemoryDatabase();

    Login:

    app.route('/api/login')
      .post(login);
    import {Request, Response} from 'express';
    import {db} from './database';
    import {DbUser} from './db-user';
    import * as argon2 from 'argon2';
    import {randomBytes} from './security.utils';
    import {sessionStore} from './session-store';
    
    
    export function login(req: Request, res: Response) {
    
      const info = req.body;
      const user = db.findUserByEmail(info.email);
    
      if (!user) {
        res.sendStatus(403);
      } else {
        loginAndBuildResponse(info, user, res);
      }
    }
    
    async function loginAndBuildResponse(credentials: any, user: DbUser, res: Response) {
      try {
        const sessionId = await attemptLogin(credentials, user);
        res.cookie('SESSIONID', sessionId, {httpOnly: true, secure: true});
        res.status(200).json({id: user.id, email: user.email});
      } catch (err) {
        res.sendStatus(403);
      }
    }
    
    
    async function attemptLogin(info: any, user: DbUser) {
      const isPasswordValid = await argon2.verify(user.passwordDigest, info.password);
    
      if (!isPasswordValid) {
        throw new Error('Password Invalid');
      }
    
      const sessionId = await randomBytes(32).then(bytes => bytes.toString('hex'));
      sessionStore.createSession(sessionId, user);
    
      return sessionId;
    }

    Logout:

    app.route('/api/logout')
      .post(logout);
    import {Response, Request} from 'express';
    import {sessionStore} from './session-store';
    
    export const logout = (req: Request, res: Response) => {
      console.log(req.cookies['SESSIONID']);
      const sessionId = req.cookies['SESSIONID'];
      sessionStore.destroySession(sessionId);
      res.clearCookie('SESSIONID');
      res.sendStatus(200);
    };
  • 相关阅读:
    WEP编码格式
    OSK VFS read数据流分析
    科学剖析濒死体验 "复生"者讲述"死"前1秒钟
    Android的开发相对于tizen的开发难度
    minix文件系统分析
    贴给小程序(1) 查找第一个0值
    Linux下的QQ
    OSK USB 驱动
    LRU算法之hash+list实现(转)
    插入排序
  • 原文地址:https://www.cnblogs.com/Answer1215/p/7546707.html
Copyright © 2020-2023  润新知