nest-phylax
Version:
Security library for NestJS
451 lines (351 loc) • 10.1 kB
Markdown
<p align="center">
<a href="http://nestjs.com"><img alt="Nest Logo" src="https://nestjs.com/img/logo-small.svg" width="120"></a>
</p>
<h1 align="center">
nest-phylax
</h1>
<p align="center">
A <a href="https://github.com/nestjs/nest">Nest</a> based security module for authentication and authorization.
</p>
**Table of Contents**
- [Features](#features)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Module Configuration](#module-configuration)
- [Async Configuration](#async-configuration)
- [Security Decorators](#security-decorators)
- [JWT Utilities](#jwt-utilities)
- [API Reference](#api-reference)
- [Dependencies](#dependencies)
- [Development](#development)
- [Contributing](#contributing)
- [License](#license)
- [Author](#author)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Features
- JWT Authentication with role-based claims
- Security Decorators for route protection
- Claims extraction from JWT tokens
- Role-based access control
- Public route marking
## Installation
```bash
npm install --save nest-phylax jsonwebtoken /passport
```
## Setup
### User Model
Phylax's jwt-auth module requires a user model to be provided. This model must implement the `User` interface. Here are examples for different database implementations:
#### Basic Implementation
```typescript
import { User as NpUser } from 'nest-phylax';
export class User implements NpUser {
id: string;
email: string;
/**
* Get the username of the user
* @returns The username of the user
*
* This method is used to get the username of the user from the user object which is used to find the user in the database
*/
getUsername(): string {
return this.email;
}
}
```
#### Mongoose Implementation
```typescript
import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';
import { User as NpUser } from 'nest-phylax';
()
export class User extends Document implements NpUser {
({ required: true })
email: string;
({ required: true })
password: string;
({ default: 'user' })
role: string;
getUsername(): string {
return this.email;
}
}
export const UserSchema = SchemaFactory.createForClass(User);
UserSchema.methods.getUsername = function () {
return this.email;
};
```
#### TypeORM Implementation
```typescript
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
import { Type } from 'nest-phylax';
()
export class User implements NpUser {
('uuid')
id: string;
({ unique: true })
email: string;
()
password: string;
({ default: 'user' })
role: string;
getUsername(): string {
return this.email;
}
}
```
### User Repository
The user repository is used to find the user in the database. It must implement the `UserRepository` interface. Here are examples for different database implementations:
#### Basic Implementation
```typescript
import { UserRepository } from 'nest-phylax';
export class UserRepository implements UserRepository {
async findOneById(id: string): Promise<User> {
// Logic to find the user by id
}
async getUserRole(user: User): Promise<string> {
// Logic to get the role of the user
}
async getRefreshTokens(user: User): Promise<string[]> {
// Logic to get the refresh tokens of the user
}
}
```
#### Mongoose Implementation
```typescript
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';
import { UserRepository } from 'nest-phylax';
import { User } from './user.schema';
()
export class UserRepository implements UserRepository {
constructor(
(User.name) private readonly userModel: Model<User>
) {}
async findOneById(id: string): Promise<User> {
return this.userModel.findById(id).exec();
}
}
```
#### TypeORM Implementation
```typescript
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { UserRepository as NpUserRepository } from 'nest-phylax';
import { User } from './user.entity';
()
export class UserRepository implements NpUserRepository {
constructor(
(User)
private readonly userRepository: Repository<User>
) {}
/**
* Find a user by their ID
* @param id The ID of the user to find
* @returns The user with the given ID
*/
async findOneById(id: string): Promise<User> {
return this.userRepository.findOne({ where: { id } });
}
/**
* Get the role of a user
* @param user The user to get the role of
* @returns The role of the user
*
* This is added to the user repository to offer flexibility on how user roles are supplied to the jwt auth * guard
*/
async getUserRole(user: User): Promise<string> {
return user.role;
}
/**
* Get the refresh tokens of a user
* @param user The user to get the refresh tokens of
* @returns The refresh tokens of the user
*/
async getRefreshTokens(user: User): Promise<string[]> {
return user.refreshTokens;
}
}
```
### Password Encoder
The password encoder is used to encode and decode passwords. It must implement the `PasswordEncoder` interface. Here are examples for different password encoder implementations:
#### Basic Implementation
```typescript
import { PasswordEncoder } from 'nest-phylax';
export class PasswordEncoder implements PasswordEncoder {
encode(password: string): Promise<string> {
/**
* Logic to encode the password and return a hash
*/
}
compare(password: string, hash: string): Promise<boolean> {
/**
* Logic to compare the password with the hash and return a boolean
*/
}
}
```
Import the `JwtAuthModule` into the root `AppModule` and use the `forRoot()` method to configure it:
```typescript
import { Module } from '@nestjs/common';
import { JwtAuthModule } from 'nest-phylax';
import { UserRepository } from './user.repository';
import { PasswordEncoder } from './password.encoder';
({
imports: [
JwtAuthModule.forRoot({
accessTokenConfig: {
secretKey: '{YOUR_SECRET_KEY}',
expiresIn: '1h',
},
userRepositoryProvider: {
useClass: UserRepository,
},
passwordEncoderProvider: {
useClass: PasswordEncoder,
},
}),
],
})
export class AppModule {}
```
## Module Configuration
The `forRoot()` method accepts the following options:
```typescript
interface JwtAuthModuleOptions {
/**
* The configuration for the access token
*/
accessTokenConfig: TokenConfig;
/**
* The provider for the user repository
*/
userRepositoryProvider: Provider;
/**
* The provider for the password encoder
*/
passwordEncoderProvider: Provider;
}
```
#### Token Config
```typescript
interface TokenConfig {
/**
* The secret key for the JWT token
*/
secretKey: string;
/**
* The expiration time for the JWT token
*/
expiresIn: StringValue;
}
type StringValue =
| `${number}`
| `${number}${UnitAnyCase}`
| `${number} ${UnitAnyCase}`;
type UnitAnyCase = Unit | Uppercase<Unit> | Lowercase<Unit>;
```
String values can be in the following formats:
- `1h`
- `1d`
- `1w`
- `1m`
- `1y`
etc.
## Security Decorators
### Public Routes
```typescript
import { Public } from 'nest-phylax';
('api')
export class ApiController {
()
('public')
getPublicData() {
return { message: 'Anyone can see this' };
}
}
```
### Role-Based Access
Nest Phylax offers a `HasRole` decorator to restrict access to specific roles. The roles have to be added to the jwt claims.
```typescript
import { HasRole, Claims } from 'nest-phylax';
('api')
export class ApiController {
('admin')
('admin')
getAdminData(() claims) {
return {
message: 'Only admins can see this',
user: claims.sub,
};
}
}
```
## JWT Utilities
### Token Generation
Nest phylax provides a wrapper around the `jsonwebtoken` library to generate and verify JWTs.
```typescript
import { JwtUtil, ClaimsDto } from 'nest-phylax';
()
export class AuthService {
constructor(private readonly jwtUtil: JwtUtil) {}
generateToken(user: User) {
return this.jwtUtil.generateToken({
user,
claims: new ClaimsDto({ id: user.id, role: user.role }),
});
}
}
```
### Token Verification
```typescript
()
export class AuthService {
verifyToken(token: string) {
return this.jwtUtil.verifyJwt(token);
}
}
```
## API Reference
### Decorators
| Decorator | Description |
| --------------------------- | ------------------------------------ |
| `()` | Marks a route as publicly accessible |
| `()` | Extracts claims from the JWT token |
| `(roles: string[])` | Restricts access to specific roles |
### JWT Utilities
```typescript
class JwtUtil {
generateToken({
user,
claims,
secretKey,
signOptions,
}: {
user: User;
claims?: ClaimsDto;
secretKey?: string;
signOptions?: SignOptions;
}): string;
verifyJwt(token: string, secretKey?: string): ClaimsDto;
}
```
## Required Dependencies
- /common
- /core
- /platform-express
- jsonwebtoken
## Development
```bash
# Install dependencies
npm install
# Build the project
npm run build
# Run tests
npm test
```
## License
This project is released under the terms of the [ISC License](LICENSE).
## Author
Konan