[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);
};
复制代码

 

posted @   Zhentiw  阅读(313)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
历史上的今天:
2016-09-19 [Angular 2] Create Shareable Angular 2 Components
2016-09-19 [Angular 2] Import custom module
2016-09-19 [Angular 2] Understanding Pure & Impure pipe
点击右上角即可分享
微信分享提示