@rs-tech-hub/nestjs-activation-token

Secure activation token management for NestJS applications. Generate, validate, and manage time-limited activation tokens for user verification workflows with automatic expiration, revocation, and scheduled cleanup.

โœจ Features

  • ๐Ÿ” Secure token generation with SHA-256 hashing
  • โฐ Automatic token expiration (default: 2 days)
  • โœ… Token validation and verification
  • ๐Ÿ”„ Token revocation for previously issued tokens
  • ๐Ÿงน Automatic cleanup of expired/revoked/used tokens (60-day retention)
  • โฒ๏ธ Scheduled cleanup every 6 hours via built-in scheduler
  • ๐Ÿ›ก๏ธ One-time use token enforcement
  • ๐Ÿ“Š Built-in token lifecycle tracking
  • ๐Ÿ—‘๏ธ Cleanup on application startup

๐Ÿ“‹ Prerequisites

  • Node.js >= 18
  • TypeScript >= 5.1.0
  • NestJS >= 11.1.6
  • Prisma ORM v7.0+
  • PostgreSQL database

๐Ÿš€ Quick Start

Getting Started

This module is included in the NestJS Auth Bundle demo application. You can find the complete implementation at:

๐Ÿ‘‰ GitHub: RuffSantiDev

The demo application includes all required dependencies and provides a working example of how to use this module.

Prisma Schema Setup

Include the Activation Token Prisma schema in your project:

// In your main schema.prisma or import the schema
model ActivationToken {
  id        String    @id @unique @default(cuid())
  userId    String
  tokenHash String    @unique
  createdAt DateTime  @default(now())
  expiresAt DateTime
  usedAt    DateTime?
  isRevoked Boolean   @default(false)
  revokedAt DateTime?
  User      User      @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@index([userId])
}

Module Registration

Import the module in your NestJS application:

import { Module } from "@nestjs/common";
import { ActivationTokenModule } from "@rs-tech-hub/nestjs-activation-token";

@Module({
  imports: [ActivationTokenModule],
})
export class AppModule {}

The module automatically registers a scheduler that:

  • Runs cleanup on application startup
  • Executes cleanup every 6 hours to remove expired/revoked/used tokens older than 60 days

๏ฟฝ API Reference

ActivationTokenService

create(userId: string)

Creates a new activation token for a user.

Parameters:

  • userId (string): The user ID to associate with the token

Returns: Promise<ActivationTokenCreateServiceOutput>

Output Fields:

  • id (string): Token ID
  • userId (string): Associated user ID
  • tokenHash (string): Hashed token (stored in database)
  • activationKey (string): Plain token to send to user (not stored)
  • createdAt (Date): Token creation timestamp
  • expiresAt (Date): Token expiration timestamp (2 days from creation)
const tokenData = await activationTokenService.create("user-123");
// Returns:
// {
//   id: 'token-id',
//   userId: 'user-123',
//   tokenHash: 'hashed-token',
//   activationKey: 'plain-token-to-send', // Send this to user
//   createdAt: Date,
//   expiresAt: Date // 2 days from creation
// }

validate(input: ActivationTokenValidateServiceInput)

Validates an activation token and marks it as used.

Parameters:

  • input.userId (string): The user ID
  • input.activationKey (string): The activation key received from user

Returns: Promise<boolean>

Throws:

  • activation-token-error:token-not-found if no valid token exists
  • activation-token-error:invalid if token doesn't match or is expired
const isValid = await activationTokenService.validate({
  userId: "user-123",
  activationKey: "token-from-email",
});

verify(input: ActivationTokenVerifyServiceInput)

Verifies if an activation key matches a token hash (without marking as used).

Parameters:

  • input.activationKey (string): The activation key to verify
  • input.tokenHash (string): The stored token hash

Returns: Promise<boolean>

const matches = await activationTokenService.verify({
  activationKey: "token-key",
  tokenHash: "stored-hash",
});

invalidatePreviousUserTokens(userId: string)

Revokes all unused tokens for a specific user by setting isRevoked to true and recording revokedAt timestamp.

Parameters:

  • userId (string): The user ID

Returns: Promise<boolean> - Returns true if tokens were found and revoked, false if no tokens found

await activationTokenService.invalidatePreviousUserTokens("user-123");

cleanupExpiredTokens()

Removes all expired, revoked, or used tokens that are older than 60 days from the database. This method is automatically called:

  • On application startup
  • Every 6 hours via the built-in scheduler

Returns: Promise<{ success: boolean; count: number }>

Response:

  • success (boolean): True if any tokens were deleted
  • count (number): Number of tokens deleted
const result = await activationTokenService.cleanupExpiredTokens();
// Returns: { success: true, count: 15 }

๐Ÿ”„ Common Workflows

User Registration Flow

@Injectable()
export class RegistrationService {
  constructor(
    private userService: UserService,
    private activationTokenService: ActivationTokenService,
    private emailService: EmailService,
  ) {}

  async registerUser(email: string, password: string) {
    // 1. Create user account (inactive)
    const user = await this.userService.create({
      email,
      password,
      isActive: false,
    });

    // 2. Generate activation token
    const token = await this.activationTokenService.create(user.id);

    // 3. Send activation email
    await this.emailService.sendActivationEmail(email, token.activationKey);

    return { userId: user.id, message: "Activation email sent" };
  }

  async activateAccount(userId: string, activationKey: string) {
    // Validate and mark token as used
    const isValid = await this.activationTokenService.validate({
      userId,
      activationKey,
    });

    if (!isValid) {
      throw new Error("Invalid or expired activation token");
    }

    // Activate user account
    await this.userService.update(userId, { isActive: true });

    return { message: "Account activated successfully" };
  }
}

Resend Activation Token

async resendActivationToken(userId: string) {
  // Revoke previous tokens
  await this.activationTokenService.invalidatePreviousUserTokens(userId);

  // Create new token
  const token = await this.activationTokenService.create(userId);

  // Send new email
  await this.emailService.sendActivationEmail(
    userEmail,
    token.activationKey
  );
}

๐Ÿ”’ Security Features

  • SHA-256 Hashing: Activation keys are hashed before storage
  • One-time Use: Tokens are marked as used after validation
  • Expiration: Tokens automatically expire after 2 days
  • Revocation: Previous tokens can be revoked when issuing new ones
  • Secure Generation: Uses crypto.randomBytes for token generation
  • Automatic Cleanup: Removes old tokens (60-day retention) every 6 hours
  • Cascade Deletion: Tokens are deleted when associated user is deleted

๐Ÿ“Š Token Lifecycle

  1. Created: Token generated with 2-day expiration
  2. Sent: Plain activationKey sent to user via email/SMS
  3. Used: Token validated and marked with usedAt timestamp
  4. Revoked: Token marked as revoked with revokedAt timestamp (optional)
  5. Expired: Token passes expiration date
  6. Cleaned: Token deleted 60 days after expiration/revocation/use

๐Ÿ“ Data Types

ActivationTokenValidateServiceInput

FieldTypeRequiredDescription
userIdstringโœ…User ID to validate token for
activationKeystringโœ…Activation key from email/SMS

ActivationTokenVerifyServiceInput

FieldTypeRequiredDescription
activationKeystringโœ…Activation key to verify
tokenHashstringโœ…Stored token hash to compare

ActivationTokenCreateServiceOutput

FieldTypeDescription
idstringToken ID
userIdstringAssociated user ID
tokenHashstringHashed token (stored)
activationKeystringPlain token (send to user)
createdAtDateToken creation time
expiresAtDateToken expiration time

โš ๏ธ Error Codes

Error CodeDescription
activation-token-error:invalidToken is invalid or expired
activation-token-error:token-not-foundNo token found for user

๐Ÿ’ก Best Practices

  1. Send tokens immediately: Generate and send tokens right after user registration
  2. Automatic cleanup: Built-in scheduler handles cleanup - no manual intervention needed
  3. Revoke on resend: Always revoke previous tokens when issuing new ones using invalidatePreviousUserTokens()
  4. Never expose token hash: Only send the activationKey to users
  5. Set reasonable expiration: Default 2 days is suitable for most cases
  6. Handle errors gracefully: Catch and handle token-not-found and invalid errors appropriately
  7. Use validation over verification: Use validate() for user activation as it marks tokens as used

๐Ÿ—‚๏ธ Database Schema

The package uses the following Prisma model:

model ActivationToken {
  id        String    @id @unique @default(cuid())
  userId    String
  tokenHash String    @unique
  createdAt DateTime  @default(now())
  expiresAt DateTime
  usedAt    DateTime?
  isRevoked Boolean   @default(false)
  revokedAt DateTime?
  User      User      @relation(fields: [userId], references: [id], onDelete: Cascade)

  @@index([userId])
}

Fields:

  • id: Unique token identifier
  • userId: Associated user identifier (indexed)
  • tokenHash: SHA-256 hash of the activation key (unique)
  • createdAt: Token creation timestamp
  • expiresAt: Token expiration timestamp
  • usedAt: Timestamp when token was validated (nullable)
  • isRevoked: Boolean flag indicating if token was manually revoked
  • revokedAt: Timestamp when token was revoked (nullable)

๐Ÿ“… Release Notes

1.1.0

Breaking Changes

  • Updated ActivationToken Prisma model: replaced invalidatedAt with revokedAt field
  • Changed model field from invalidatedAt DateTime? to:
    • isRevoked Boolean @default(false)
    • revokedAt DateTime?
  • Updated cleanupExpiredTokens() method to filter by revokedAt instead of invalidatedAt

New Features

  • Added ActivationTokenScheduler to automatically clean up expired tokens every 6 hours
  • Cleanup now runs automatically on application startup
  • Enhanced cleanup to include revoked and used tokens with 60-day retention period
  • Cleanup now returns detailed response with { success: boolean; count: number }

1.0.0

  • Initial release
  • Secure token generation with SHA-256 hashing
  • Token validation and verification
  • Token expiration (2 days)
  • Manual cleanup method

๐Ÿ“„ License

This package requires a valid RS Tech Hub commercial license key. The software is protected by intellectual property laws and is provided under a commercial license agreement.

License Terms

๐Ÿ› Issues & Support

For issues, feature requests, or questions, please contact insights@rs-tech-hub.com


Built with โค๏ธ by RS Tech Hub