mirror of
https://github.com/apricote/Listory.git
synced 2026-01-13 21:21:02 +00:00
feat(frontend): manage API tokens in Frontend
This commit is contained in:
parent
d0ca2b967e
commit
ac0f9ff5d3
13 changed files with 484 additions and 33 deletions
|
|
@ -3,6 +3,7 @@ import {
|
|||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Res,
|
||||
UseFilters,
|
||||
|
|
@ -11,13 +12,14 @@ import {
|
|||
import { ApiBody, ApiTags } from "@nestjs/swagger";
|
||||
import type { Response } from "express";
|
||||
import { User } from "../users/user.entity";
|
||||
import { ApiToken } from "./api-token.entity";
|
||||
import { AuthSession } from "./auth-session.entity";
|
||||
import { AuthService } from "./auth.service";
|
||||
import { COOKIE_REFRESH_TOKEN } from "./constants";
|
||||
import { AuthAccessToken } from "./decorators/auth-access-token.decorator";
|
||||
import { ReqUser } from "./decorators/req-user.decorator";
|
||||
import { ApiTokenDto } from "./dto/api-token.dto";
|
||||
import { CreateApiTokenRequestDto } from "./dto/create-api-token-request.dto";
|
||||
import { NewApiTokenDto } from "./dto/new-api-token.dto";
|
||||
import { RefreshAccessTokenResponseDto } from "./dto/refresh-access-token-response.dto";
|
||||
import { RevokeApiTokenRequestDto } from "./dto/revoke-api-token-request.dto";
|
||||
import {
|
||||
|
|
@ -68,23 +70,38 @@ export class AuthController {
|
|||
async createApiToken(
|
||||
@ReqUser() user: User,
|
||||
@Body("description") description: string
|
||||
): Promise<ApiToken> {
|
||||
return this.authService.createApiToken(user, description);
|
||||
): Promise<NewApiTokenDto> {
|
||||
const apiToken = await this.authService.createApiToken(user, description);
|
||||
|
||||
return {
|
||||
id: apiToken.id,
|
||||
description: apiToken.description,
|
||||
token: apiToken.token,
|
||||
createdAt: apiToken.createdAt,
|
||||
};
|
||||
}
|
||||
|
||||
@Get("api-tokens")
|
||||
@AuthAccessToken()
|
||||
async listApiTokens(@ReqUser() user: User): Promise<ApiToken[]> {
|
||||
return this.authService.listApiTokens(user);
|
||||
async listApiTokens(@ReqUser() user: User): Promise<ApiTokenDto[]> {
|
||||
const apiTokens = await this.authService.listApiTokens(user);
|
||||
|
||||
return apiTokens.map((apiToken) => ({
|
||||
id: apiToken.id,
|
||||
description: apiToken.description,
|
||||
prefix: apiToken.token.slice(0, 12),
|
||||
createdAt: apiToken.createdAt,
|
||||
revokedAt: apiToken.revokedAt,
|
||||
}));
|
||||
}
|
||||
|
||||
// This endpoint does not validate that the token belongs to the logged in user.
|
||||
// Once the token is known, it does not matter which account makes the actual
|
||||
// request to revoke it.
|
||||
@Delete("api-tokens")
|
||||
@Delete("api-tokens/:id")
|
||||
@ApiBody({ type: RevokeApiTokenRequestDto })
|
||||
@AuthAccessToken()
|
||||
async revokeApiToken(@Body("token") token: string): Promise<void> {
|
||||
return this.authService.revokeApiToken(token);
|
||||
async revokeApiToken(
|
||||
@ReqUser() user: User,
|
||||
@Param("id") id: string
|
||||
): Promise<void> {
|
||||
return this.authService.revokeApiToken(user, id);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { ForbiddenException, Injectable } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { JwtService } from "@nestjs/jwt";
|
||||
import { randomBytes } from "crypto";
|
||||
import { User } from "../users/user.entity";
|
||||
import { UsersService } from "../users/users.service";
|
||||
import { ApiToken } from "./api-token.entity";
|
||||
|
|
@ -8,7 +9,6 @@ import { ApiTokenRepository } from "./api-token.repository";
|
|||
import { AuthSession } from "./auth-session.entity";
|
||||
import { AuthSessionRepository } from "./auth-session.repository";
|
||||
import { LoginDto } from "./dto/login.dto";
|
||||
import { randomBytes } from "crypto";
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
|
|
@ -129,11 +129,13 @@ export class AuthService {
|
|||
return this.apiTokenRepository.scoped.byUser(user).getMany();
|
||||
}
|
||||
|
||||
async revokeApiToken(token: string): Promise<void> {
|
||||
const apiToken = await this.findApiToken(token);
|
||||
async revokeApiToken(user: User, id: string): Promise<void> {
|
||||
const apiToken = await this.apiTokenRepository.findOneBy({ user, id });
|
||||
|
||||
apiToken.revokedAt = new Date();
|
||||
await this.apiTokenRepository.save(apiToken);
|
||||
if (apiToken && apiToken.revokedAt == null) {
|
||||
apiToken.revokedAt = new Date();
|
||||
await this.apiTokenRepository.save(apiToken);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
7
src/auth/dto/api-token.dto.ts
Normal file
7
src/auth/dto/api-token.dto.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export interface ApiTokenDto {
|
||||
id: string;
|
||||
description: string;
|
||||
prefix: string;
|
||||
createdAt: Date;
|
||||
revokedAt: Date | null;
|
||||
}
|
||||
6
src/auth/dto/new-api-token.dto.ts
Normal file
6
src/auth/dto/new-api-token.dto.ts
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export interface NewApiTokenDto {
|
||||
id: string;
|
||||
description: string;
|
||||
token: string;
|
||||
createdAt: Date;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue