@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 IDuserId(string): Associated user IDtokenHash(string): Hashed token (stored in database)activationKey(string): Plain token to send to user (not stored)createdAt(Date): Token creation timestampexpiresAt(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 IDinput.activationKey(string): The activation key received from user
Returns: Promise<boolean>
Throws:
activation-token-error:token-not-foundif no valid token existsactivation-token-error:invalidif 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 verifyinput.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 deletedcount(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
- Created: Token generated with 2-day expiration
- Sent: Plain
activationKeysent to user via email/SMS - Used: Token validated and marked with
usedAttimestamp - Revoked: Token marked as revoked with
revokedAttimestamp (optional) - Expired: Token passes expiration date
- Cleaned: Token deleted 60 days after expiration/revocation/use
๐ Data Types
ActivationTokenValidateServiceInput
| Field | Type | Required | Description |
|---|---|---|---|
| userId | string | โ | User ID to validate token for |
| activationKey | string | โ | Activation key from email/SMS |
ActivationTokenVerifyServiceInput
| Field | Type | Required | Description |
|---|---|---|---|
| activationKey | string | โ | Activation key to verify |
| tokenHash | string | โ | Stored token hash to compare |
ActivationTokenCreateServiceOutput
| Field | Type | Description |
|---|---|---|
| id | string | Token ID |
| userId | string | Associated user ID |
| tokenHash | string | Hashed token (stored) |
| activationKey | string | Plain token (send to user) |
| createdAt | Date | Token creation time |
| expiresAt | Date | Token expiration time |
โ ๏ธ Error Codes
| Error Code | Description |
|---|---|
activation-token-error:invalid | Token is invalid or expired |
activation-token-error:token-not-found | No token found for user |
๐ก Best Practices
- Send tokens immediately: Generate and send tokens right after user registration
- Automatic cleanup: Built-in scheduler handles cleanup - no manual intervention needed
- Revoke on resend: Always revoke previous tokens when issuing new ones using
invalidatePreviousUserTokens() - Never expose token hash: Only send the
activationKeyto users - Set reasonable expiration: Default 2 days is suitable for most cases
- Handle errors gracefully: Catch and handle
token-not-foundandinvaliderrors appropriately - 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 identifieruserId: Associated user identifier (indexed)tokenHash: SHA-256 hash of the activation key (unique)createdAt: Token creation timestampexpiresAt: Token expiration timestampusedAt: Timestamp when token was validated (nullable)isRevoked: Boolean flag indicating if token was manually revokedrevokedAt: Timestamp when token was revoked (nullable)
๐ Release Notes
1.1.0
Breaking Changes
- Updated
ActivationTokenPrisma model: replacedinvalidatedAtwithrevokedAtfield - Changed model field from
invalidatedAt DateTime?to:isRevoked Boolean @default(false)revokedAt DateTime?
- Updated
cleanupExpiredTokens()method to filter byrevokedAtinstead ofinvalidatedAt
New Features
- Added
ActivationTokenSchedulerto 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
- Commercial use requires a valid license key
- Development testing allowed with free trial
- Production deployment requires a paid license from https://rstechhub.gumroad.com
- Contact insights@rs-tech-hub.com for questions and inquiries
๐ Issues & Support
For issues, feature requests, or questions, please contact insights@rs-tech-hub.com
Built with โค๏ธ by RS Tech Hub