/word-management

This is a word cards management app built with Node.js, Express and Sqlite3.

Primary LanguageTypeScript

Word Card Management App

This is a word cards management app built with Node.js, Express and Sqlite3.

Setup

  1. Install dependencies
npm install
  1. Start the app
npm run dev

After that, you will see the terminal display:

App listening on the port 3000

Build

  1. Generate dist/ outputs
npm run build
  1. Start the app
npm run start

Test

npm run test

Users

When starting the app, the users table inserts 2 users by default

  • User Tom, administrator identity
  • User Mary, normal user

API

All API are prefixed with /api/v1, for example, you want to test login api, it's /api/v1/login.

API METHOD Authorization PARAMS/BODY PERMISSION DESCRIPTION
/login POST false {"uername": "Mary"} useradmin User login
/users GET true - admin Get all users
/user/:id GET true - adminuser Get user by id
/revoke-token POST true {"userid": 1} admin Revoke user's token
/cards POST true {"user_input": "test"} adminuser Create word card
/cards GET true pageNumber=1&pageSize=10 adminuser Get cards with pagination
/cards/:id GET true - adminuser Get card by id
/cards/:id PUT true {"user_input": "modify01"} adminuser Modify card
/cards/:id DELETE true - adminuser Delete card

About Two-Factor Authentication

Notes: This feature has not yet been implemented

Workflow Introduce

  1. When the user logs in for the first time, enter the username and password.
  2. After the server verifies that the username and password are correct, the speakeasy module is used to generate a two-factor authentication machine identification code and secret key.
  3. The server returns the identification code information to the user, prompts the user to open the Authenticator APP to scan the identification code and add it to the APP.
  4. The user opens the Authenticator APP, scans the identification code, and binds the device. At this time, the APP can generate a 6-digit one-time password (TOTP) based on the secret key and the current time.
  5. The server prompts the user to enter the TOTP generated by the APP to complete the two-factor verification.
  6. The user copies the 6-bit TOTP from the APP, and the input is submitted to the server. The server side uses the speakeasy module to verify whether the incoming TOTP is correct based on the bound key and time.
  7. If the verification is passed, the two-factor authentication is completed, and the server generates a JWT containing the verified flag and returns it to the user.
  8. When the user logs in later, after entering the username and password, the server verifies and generates the TOTP, and the user copies the TOTP submission from the APP.
  9. The server-side verifies the TOTP, and if it is correct, it can log in directly without scanning the code to bind.
  10. The user also needs to enter the current TOTP when invoking a function that requires two-factor authentication.

Aspects to consider when implementing

  1. The user table needs to add fields:
  • totp_secret: Used to store TOTP keys generated by the speakeasy module
  • totp_verified: Marks if two-factor authentication is enabled
    • When the user logs in for the first time, totp_verified = false
    • After the user binds two-factor authentication, set the totp_verified to true
    • Follow-up login judgment totp_verified:
      • If false, it means unbound, allow username and password to log in
      • If true, it means bound, and additional TOTP verification is required when logging in.
    • When the user turns off two-factor authentication, set the totp_verified to false
  1. Login and two-factor authentication middleware can be designed as follows:
  • User Login: Verify Username Password -- > Detect totp_verified -- > If enabled, return scan binding prompt
  • Two-factor authentication middleware: verify username and password -- > detect totp_verified -- > generate TOTP and send it to the user -- > verify the TOTP submitted by the user
  1. Insert authentication middleware before routes that require two-factor authentication, and verify TOTP
  2. After totp_verified is detected in some common login scenarios, the two-factor verification logic needs to be skipped 5.The generated JWT needs to include a custom claim that identifies two-factor verification
  3. Entry to enable and close two-factor authentication, storage totp_secret and update totp_verified
  4. Note the secure storage of TOTP keys, do not write to database cleartext

Handling High Load and Concurrency

This app implements several strategies to handle high traffic and potential race conditions when updating data:

  1. For concurrent POST and PUT requests to create/update cards, a global taskQueue middleware is used. All write tasks are pushed to this queue and executed sequentially. The queue has a retry mechanism - if a task fails, it will be retried up to 2 times with a delay between retries.
this.router.post(
  this.path,
  ...,
  queueMiddleware(this.cards.createCard.bind(this.cards)),
);
  1. The PUT /cards endpoint for updating cards uses database transactions to ensure data integrity under high concurrency. This guarantees that partial/incomplete updates do not happen.

  2. To protect against excessive requests and traffic spikes, a custom rate limiting middleware is used. If a client exceeds the request limit, a 429 Too Many Requests response is returned to inform the client to back off. This prevents any single client from overwhelming the application.

// Limit requests to 60 per minute
app.use(
  createRateLimitMiddleware({
    max: 60,
    windowMs: 60 * 1000
  })
);

Security and Input Validation

Input Validation

Input validation is implemented using a middleware to protect the input value of user_input. It filters out special characters and prevents SQL injection.

Security

For security purposes, a custom cors middleware is implemented to configure the secure origin using environment variables.

this.app.use(cors({ origin: ORIGIN, credentials: CREDENTIALS }));

A security headers middleware, securityHeadersMiddleware, is also written to enhance security measures.

export default function securityHeadersMiddleware(req, res, next) {
  // Set the XSS guard header
  res.setHeader('X-XSS-Protection', '1; mode=block');

  // Disable content sniffing
  res.setHeader('X-Content-Type-Options', 'nosniff');

  // Remove leaky information
  res.removeHeader('X-Powered-By');

  next();
}