npm package: @sftech/nestjs-auth
Description: Authentication and authorization library for NestJS applications with OAuth2/OIDC support, JWT validation, and CASL-based authorization.
Latest version: 1.5.2

# @sftech/nestjs-auth

Authentication and authorization library for NestJS applications with OAuth2/OIDC support, JWT validation, and CASL-based authorization.

## Installation

```bash
npm install @sftech/nestjs-auth
```

## Peer Dependencies

- `@sftech/nestjs-core >= 1.0.0`
- `@sftech/nestjs-db >= 1.0.0`
- `@nestjs/common`
- `@casl/ability`

## Features

- OAuth2/OIDC token validation
- JWT token verification via JWKS (JSON Web Key Set)
- CASL-based authorization
- Auth-aware CRUD operations (extends nestjs-db)
- User context injection throughout application layers
- Guards and decorators for route protection

## Configuration

```typescript
import { NestjsAuthModule } from '@sftech/nestjs-auth';

@Module({
  imports: [
    NestjsAuthModule.register({
      authProvider: 'oauth',
      oauthValidationUrl: 'https://your-oauth-provider/userinfo',
    }),
  ],
})
export class AppModule {}
```

### Configuration Options

Add to your `app.config.json`:

```json
{
  "AUTH_PROVIDER": "oauth",
  "AUTH_OAUTH_VALIDATION_URL": "https://keycloak.example.com/realms/myrealm/protocol/openid-connect/userinfo"
}
```

| Parameter | Type | Description |
|-----------|------|-------------|
| `AUTH_PROVIDER` | `'oauth' \| 'local'` | Authentication provider type |
| `AUTH_OAUTH_VALIDATION_URL` | `string` | OAuth userinfo endpoint for token validation |

## API Reference

### Application Layer

| Class | Purpose |
|-------|---------|
| `BaseCrudAuthBundle<TDbModel>` | Auth-aware CRUD bundle with user context |
| `AuthGetByIdUsecase` | Get by ID with auth check |
| `AuthGetAllUsecase` | Get all with auth filtering |
| `AuthGetByUserUsecase` | Get entities for current user |
| `AuthInsertUsecase` | Insert with user context |
| `AuthUpdateUsecase` | Update with auth check |
| `AuthDeleteUsecase` | Delete with auth check |
| `AuthBulkSaveUsecase` | Bulk save with auth |
| `AuthOdataUsecase` | OData with auth filtering |

### Infrastructure Layer

| Class | Purpose |
|-------|---------|
| `BaseAuthDbEntity` | Base entity with userId field |
| `BaseAuthDbRepository<TDbModel, TDbEntity>` | Repository with auth filtering |
| `BaseAuthDbMapper<TDbModel, TDbEntity>` | Mapper with auth fields |

### Presentation Layer

| Class | Purpose |
|-------|---------|
| `BaseCrudAuthController` | Controller with auth-aware CRUD |

---

## Base Classes & Interfaces

### BaseCrudAuthBundle<TDbModel>

Auth-aware bundle containing all CRUD use cases with user context injection. **Extends BaseCrudBundle with CASL authorization.**

**Generic type parameters:**
- `TDbModel extends IBaseAuthDbModel` - Your domain model (must include userId)

**Provides out of the box:**
- All methods from `BaseCrudBundle`
- `getAllByUserUsecase` - Get entities for current user only
- User context automatically injected via `RequestContextStorage`
- CASL ability checks on all operations

**Constructor parameters:**
- `_repo: IBaseAuthDbRepository<TDbModel>` - Auth-aware repository
- `_abilityFactory: IBaseCaslFactory` - CASL ability factory
- `_rcs: RequestContextStorage` - Request context storage

**Usage example:**
```typescript
import { BaseCrudAuthBundle, IBaseAuthDbModel } from '@sftech/nestjs-auth';
import { RequestContextStorage } from '@sftech/nestjs-core';
import { Injectable } from '@nestjs/common';

// 1. Define your domain model with userId
export class Article implements IBaseAuthDbModel {
    id?: number;
    createdAt?: Date;
    userId: string;  // Required for auth-aware entities

    title: string;
    content: string;
}

// 2. Create CASL ability factory
@Injectable()
export class ArticleAbilityFactory implements IBaseCaslFactory {
    createForUser(user: UserInfo) {
        const { can, cannot, build } = new AbilityBuilder(createMongoAbility);

        // Users can read all articles
        can('read', 'Article');

        // Users can only modify their own articles
        can(['update', 'delete'], 'Article', { userId: user.sub });

        return build();
    }
}

// 3. Create the auth bundle
@Injectable()
export class ArticleCrudAuthBundle extends BaseCrudAuthBundle<Article> {
    constructor(
        repository: ArticleRepository,
        abilityFactory: ArticleAbilityFactory,
        rcs: RequestContextStorage,
    ) {
        super(repository, abilityFactory, rcs);
    }
}

// 4. Use in controller
@Controller('articles')
@UseGuards(JwtAuthGuard)
export class ArticleController {
    constructor(private readonly articleBundle: ArticleCrudAuthBundle) {}

    @Get()
    async getAll() {
        // Returns all articles (filtered by CASL abilities)
        return this.articleBundle.getAllUsecase.execute();
    }

    @Get('my')
    async getMyArticles() {
        // Returns only current user's articles
        return this.articleBundle.getAllByUserUsecase.execute();
    }

    @Post()
    async create(@Body() dto: CreateArticleDto) {
        const article = new Article();
        article.title = dto.title;
        article.content = dto.content;
        // userId is automatically set from RequestContextStorage
        return this.articleBundle.insertUsecase.execute(article);
    }

    @Delete(':id')
    async delete(@Param('id') id: number) {
        // CASL checks if user owns this article
        return this.articleBundle.deleteUsecase.execute(id);
    }
}
```

---

### BaseAuthDbRepository<TDbModel, TDbEntity>

Abstract repository extending BaseDbRepository with auth-aware filtering. **Extend this for entities that belong to users.**

**Generic type parameters:**
- `TDbModel extends IBaseAuthDbModel` - Domain model with userId
- `TDbEntity extends IBaseAuthDbEntity` - TypeORM entity with userId

**Provides out of the box:**
- All methods from `BaseDbRepository`
- `getAllByUser(userId)` - Get all entities for a specific user
- `getByIdAndUser(id, userId)` - Get by ID with user ownership check

**You must implement:**
- `_mapper: IBaseAuthDbMapper<TDbModel, TDbEntity>` - Auth-aware mapper

**Usage example:**
```typescript
import { BaseAuthDbRepository, IBaseAuthDbMapper } from '@sftech/nestjs-auth';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';

@Injectable()
export class ArticleRepository extends BaseAuthDbRepository<Article, ArticleEntity> {
    protected readonly _mapper: IBaseAuthDbMapper<Article, ArticleEntity>;

    constructor(
        @InjectRepository(ArticleEntity)
        repo: Repository<ArticleEntity>,
        mapper: ArticleMapper,
    ) {
        super(repo);
        this._mapper = mapper;
    }

    // Custom auth-aware queries
    async findPublishedByUser(userId: string): Promise<Article[]> {
        return this.findBy({
            where: { userId, publishedAt: Not(IsNull()) },
        });
    }
}
```

---

### BaseAuthDbEntity

Base TypeORM entity with userId field. **Extend this for all user-owned entities.**

**Provides:**
- `id?: number` - Primary key (from BaseDbEntity)
- `createdAt?: Date` - Creation timestamp (from BaseDbEntity)
- `userId: string` - Owner user ID

**Usage example:**
```typescript
import { BaseAuthDbEntity } from '@sftech/nestjs-auth';
import { Entity, Column } from 'typeorm';

@Entity('articles')
export class ArticleEntity extends BaseAuthDbEntity {
    @Column()
    title: string;

    @Column('text')
    content: string;

    @Column({ nullable: true })
    publishedAt?: Date;
}
```

---

### BaseAuthDbMapper<TDbModel, TDbEntity>

Abstract mapper extending BaseDbMapper with auth field handling.

**You must implement:**
- `mapFromDb(from: TDbEntity, to?: TDbModel): TDbModel`
- `mapToDb(from: TDbModel, to?: TDbEntity): TDbEntity`

**Usage example:**
```typescript
import { BaseAuthDbMapper } from '@sftech/nestjs-auth';
import { Injectable } from '@nestjs/common';

@Injectable()
export class ArticleMapper extends BaseAuthDbMapper<Article, ArticleEntity> {
    mapFromDb(from: ArticleEntity, to?: Article): Article {
        const article = to ?? new Article();
        article.id = from.id;
        article.createdAt = from.createdAt;
        article.userId = from.userId;
        article.title = from.title;
        article.content = from.content;
        return article;
    }

    mapToDb(from: Article, to?: ArticleEntity): ArticleEntity {
        const entity = to ?? new ArticleEntity();
        if (from.id) entity.id = from.id;
        entity.userId = from.userId;
        entity.title = from.title;
        entity.content = from.content;
        return entity;
    }
}
```

---

### BaseCrudAuthController<TDbModel, TResponseDto, TCreateDto, TUpdateDto>

Abstract controller extending BaseCrudController with authentication. **Extend for auth-protected CRUD APIs.**

**Provides out of the box:**
- All endpoints from `BaseCrudController`
- `GET /my` - Get current user's entities

**Usage example:**
```typescript
import { BaseCrudAuthController } from '@sftech/nestjs-auth';
import { Controller, UseGuards } from '@nestjs/common';

@Controller('articles')
@UseGuards(JwtAuthGuard)
export class ArticleController extends BaseCrudAuthController<
    Article,
    ArticleResponseDto,
    CreateArticleDto,
    UpdateArticleDto
> {
    protected readonly _converter: ArticleDtoConverter;

    constructor(
        articleBundle: ArticleCrudAuthBundle,
        converter: ArticleDtoConverter,
    ) {
        super(articleBundle);
        this._converter = converter;
    }
}
```

---

### IBaseAuthDbModel

Interface for auth-aware domain models. **Implement for all user-owned models.**

**Required properties:**
- `id?: number` - Entity ID
- `createdAt?: Date` - Creation timestamp
- `userId: string` - Owner user ID

**Usage example:**
```typescript
import { IBaseAuthDbModel } from '@sftech/nestjs-auth';

export class Comment implements IBaseAuthDbModel {
    id?: number;
    createdAt?: Date;
    userId: string;

    articleId: number;
    text: string;
}
```

---

### IBaseCaslFactory

Interface for CASL ability factories. **Implement to define authorization rules.**

**You must implement:**
- `createForUser(user: UserInfo): MongoAbility` - Create abilities for a user

**Usage example:**
```typescript
import { IBaseCaslFactory } from '@sftech/nestjs-auth';
import { AbilityBuilder, createMongoAbility, MongoAbility } from '@casl/ability';
import { Injectable } from '@nestjs/common';

@Injectable()
export class AppAbilityFactory implements IBaseCaslFactory {
    createForUser(user: UserInfo): MongoAbility {
        const { can, cannot, build } = new AbilityBuilder(createMongoAbility);

        if (user.roles.includes('admin')) {
            // Admins can do everything
            can('manage', 'all');
        } else {
            // Regular users
            can('read', 'Article');
            can('create', 'Article');
            can(['update', 'delete'], 'Article', { userId: user.sub });

            can('read', 'Comment');
            can('create', 'Comment');
            can(['update', 'delete'], 'Comment', { userId: user.sub });
        }

        return build();
    }
}
```

---

## Complete Example

```typescript
// === DOMAIN ===

// article.model.ts
import { IBaseAuthDbModel } from '@sftech/nestjs-auth';

export class Article implements IBaseAuthDbModel {
    id?: number;
    createdAt?: Date;
    userId: string;
    title: string;
    content: string;
}

// === INFRASTRUCTURE ===

// article.entity.ts
import { BaseAuthDbEntity } from '@sftech/nestjs-auth';
import { Entity, Column } from 'typeorm';

@Entity('articles')
export class ArticleEntity extends BaseAuthDbEntity {
    @Column()
    title: string;

    @Column('text')
    content: string;
}

// article.mapper.ts
import { BaseAuthDbMapper } from '@sftech/nestjs-auth';
import { Injectable } from '@nestjs/common';

@Injectable()
export class ArticleMapper extends BaseAuthDbMapper<Article, ArticleEntity> {
    mapFromDb(from: ArticleEntity): Article {
        const a = new Article();
        a.id = from.id;
        a.createdAt = from.createdAt;
        a.userId = from.userId;
        a.title = from.title;
        a.content = from.content;
        return a;
    }

    mapToDb(from: Article, to?: ArticleEntity): ArticleEntity {
        const e = to ?? new ArticleEntity();
        if (from.id) e.id = from.id;
        e.userId = from.userId;
        e.title = from.title;
        e.content = from.content;
        return e;
    }
}

// article.repository.ts
import { BaseAuthDbRepository } from '@sftech/nestjs-auth';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';

@Injectable()
export class ArticleRepository extends BaseAuthDbRepository<Article, ArticleEntity> {
    protected readonly _mapper: ArticleMapper;

    constructor(
        @InjectRepository(ArticleEntity) repo: Repository<ArticleEntity>,
        mapper: ArticleMapper,
    ) {
        super(repo);
        this._mapper = mapper;
    }
}

// === APPLICATION ===

// article-ability.factory.ts
import { IBaseCaslFactory } from '@sftech/nestjs-auth';
import { AbilityBuilder, createMongoAbility } from '@casl/ability';
import { Injectable } from '@nestjs/common';

@Injectable()
export class ArticleAbilityFactory implements IBaseCaslFactory {
    createForUser(user: UserInfo) {
        const { can, build } = new AbilityBuilder(createMongoAbility);
        can('read', 'Article');
        can(['create', 'update', 'delete'], 'Article', { userId: user.sub });
        return build();
    }
}

// article.bundle.ts
import { BaseCrudAuthBundle } from '@sftech/nestjs-auth';
import { RequestContextStorage } from '@sftech/nestjs-core';
import { Injectable } from '@nestjs/common';

@Injectable()
export class ArticleCrudAuthBundle extends BaseCrudAuthBundle<Article> {
    constructor(
        repo: ArticleRepository,
        abilityFactory: ArticleAbilityFactory,
        rcs: RequestContextStorage,
    ) {
        super(repo, abilityFactory, rcs);
    }
}

// === MODULE ===

@Module({
    imports: [TypeOrmModule.forFeature([ArticleEntity])],
    providers: [
        ArticleMapper,
        ArticleRepository,
        ArticleAbilityFactory,
        ArticleCrudAuthBundle,
    ],
    exports: [ArticleCrudAuthBundle],
})
export class ArticleModule {}
```

---

## Development

```bash
# Build
npx nx build nestjs-auth

# Test
npx nx test nestjs-auth

# Lint
npx nx lint nestjs-auth
```

## See Also

- [@sftech/nestjs-auth-backend](../nestjs-auth-backend/README.md) - Backend OAuth service for session management
- [@sftech/nestjs-db](../nestjs-db/README.md) - Base database layer

## Changelog

See [CHANGELOG.md](./CHANGELOG.md)

## License

MIT