mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-20 18:25:21 -04:00
refactor(backend): use @hedgedoc/commons DTOs
Co-authored-by: Erik Michelson <github@erik.michelson.eu> Signed-off-by: Erik Michelson <github@erik.michelson.eu> Signed-off-by: Philip Molares <philip.molares@udo.edu>
This commit is contained in:
parent
7285c2bc50
commit
b11dbd51c8
94 changed files with 514 additions and 1642 deletions
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { Type } from 'class-transformer';
|
||||
import { IsDate, IsNumber, IsOptional, IsString } from 'class-validator';
|
||||
|
||||
import { BaseDto } from '../utils/base.dto.';
|
||||
import { TimestampMillis } from '../utils/timestamp';
|
||||
|
||||
export class ApiTokenDto extends BaseDto {
|
||||
@IsString()
|
||||
label: string;
|
||||
|
||||
@IsString()
|
||||
keyId: string;
|
||||
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
createdAt: Date;
|
||||
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
validUntil: Date;
|
||||
|
||||
@IsDate()
|
||||
@Type(() => Date)
|
||||
@IsOptional()
|
||||
lastUsedAt: Date | null;
|
||||
}
|
||||
|
||||
export class ApiTokenWithSecretDto extends ApiTokenDto {
|
||||
@IsString()
|
||||
secret: string;
|
||||
}
|
||||
|
||||
export class ApiTokenCreateDto extends BaseDto {
|
||||
@IsString()
|
||||
label: string;
|
||||
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
validUntil: TimestampMillis;
|
||||
}
|
|
@ -1,9 +1,8 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { DeepPartial } from '@hedgedoc/commons';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
|
@ -150,7 +149,7 @@ describe('ApiTokenService', () => {
|
|||
const [accessToken, secret] = service.createToken(
|
||||
user,
|
||||
'TestToken',
|
||||
undefined,
|
||||
null,
|
||||
);
|
||||
|
||||
expect(() =>
|
||||
|
@ -158,7 +157,7 @@ describe('ApiTokenService', () => {
|
|||
).not.toThrow();
|
||||
});
|
||||
it('AuthToken has wrong hash', () => {
|
||||
const [accessToken] = service.createToken(user, 'TestToken', undefined);
|
||||
const [accessToken] = service.createToken(user, 'TestToken', null);
|
||||
expect(() =>
|
||||
service.checkToken('secret', accessToken as ApiToken),
|
||||
).toThrow(TokenNotValidError);
|
||||
|
@ -167,7 +166,7 @@ describe('ApiTokenService', () => {
|
|||
const [accessToken, secret] = service.createToken(
|
||||
user,
|
||||
'Test',
|
||||
1549312452000,
|
||||
new Date(1549312452000),
|
||||
);
|
||||
expect(() => service.checkToken(secret, accessToken as ApiToken)).toThrow(
|
||||
TokenNotValidError,
|
||||
|
@ -295,18 +294,16 @@ describe('ApiTokenService', () => {
|
|||
jest
|
||||
.spyOn(apiTokenRepo, 'save')
|
||||
.mockImplementationOnce(
|
||||
async (
|
||||
apiTokenSaved: DeepPartial<ApiToken>,
|
||||
_,
|
||||
): Promise<ApiToken> => {
|
||||
async (apiTokenSaved: ApiToken, _): Promise<ApiToken> => {
|
||||
expect(apiTokenSaved.lastUsedAt).toBeNull();
|
||||
apiTokenSaved.createdAt = new Date(1);
|
||||
return apiTokenSaved;
|
||||
},
|
||||
);
|
||||
const token = await service.addToken(user, identifier, 0);
|
||||
const token = await service.addToken(user, identifier, new Date(0));
|
||||
expect(token.label).toEqual(identifier);
|
||||
expect(
|
||||
token.validUntil.getTime() -
|
||||
new Date(token.validUntil).getTime() -
|
||||
(new Date().getTime() + 2 * 365 * 24 * 60 * 60 * 1000),
|
||||
).toBeLessThanOrEqual(10000);
|
||||
expect(token.lastUsedAt).toBeNull();
|
||||
|
@ -317,18 +314,17 @@ describe('ApiTokenService', () => {
|
|||
jest
|
||||
.spyOn(apiTokenRepo, 'save')
|
||||
.mockImplementationOnce(
|
||||
async (
|
||||
apiTokenSaved: DeepPartial<ApiToken>,
|
||||
_,
|
||||
): Promise<ApiToken> => {
|
||||
async (apiTokenSaved: ApiToken, _): Promise<ApiToken> => {
|
||||
expect(apiTokenSaved.lastUsedAt).toBeNull();
|
||||
apiTokenSaved.createdAt = new Date(1);
|
||||
return apiTokenSaved;
|
||||
},
|
||||
);
|
||||
const validUntil = new Date().getTime() + 30000;
|
||||
const validUntil = new Date();
|
||||
validUntil.setTime(validUntil.getTime() + 30000);
|
||||
const token = await service.addToken(user, identifier, validUntil);
|
||||
expect(token.label).toEqual(identifier);
|
||||
expect(token.validUntil.getTime()).toEqual(validUntil);
|
||||
expect(new Date(token.validUntil)).toEqual(validUntil);
|
||||
expect(token.lastUsedAt).toBeNull();
|
||||
expect(token.secret.startsWith('hd2.' + token.keyId)).toBeTruthy();
|
||||
});
|
||||
|
@ -340,7 +336,8 @@ describe('ApiTokenService', () => {
|
|||
inValidToken.length = 201;
|
||||
return inValidToken;
|
||||
});
|
||||
const validUntil = new Date().getTime() + 30000;
|
||||
const validUntil = new Date();
|
||||
validUntil.setTime(validUntil.getTime() + 30000);
|
||||
await expect(
|
||||
service.addToken(user, identifier, validUntil),
|
||||
).rejects.toThrow(TooManyTokensError);
|
||||
|
@ -400,17 +397,17 @@ describe('ApiTokenService', () => {
|
|||
expect(tokenDto.keyId).toEqual(apiToken.keyId);
|
||||
expect(tokenDto.lastUsedAt).toBeNull();
|
||||
expect(tokenDto.label).toEqual(apiToken.label);
|
||||
expect(tokenDto.validUntil.getTime()).toEqual(
|
||||
expect(new Date(tokenDto.validUntil).getTime()).toEqual(
|
||||
apiToken.validUntil.getTime(),
|
||||
);
|
||||
expect(tokenDto.createdAt.getTime()).toEqual(
|
||||
expect(new Date(tokenDto.createdAt).getTime()).toEqual(
|
||||
apiToken.createdAt.getTime(),
|
||||
);
|
||||
});
|
||||
it('should have lastUsedAt', () => {
|
||||
apiToken.lastUsedAt = new Date();
|
||||
const tokenDto = service.toAuthTokenDto(apiToken);
|
||||
expect(tokenDto.lastUsedAt).toEqual(apiToken.lastUsedAt);
|
||||
expect(tokenDto.lastUsedAt).toEqual(apiToken.lastUsedAt.toISOString());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { ApiTokenDto, ApiTokenWithSecretDto } from '@hedgedoc/commons';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Cron, Timeout } from '@nestjs/schedule';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
@ -17,8 +18,6 @@ import {
|
|||
import { ConsoleLoggerService } from '../logger/console-logger.service';
|
||||
import { User } from '../users/user.entity';
|
||||
import { bufferToBase64Url } from '../utils/password';
|
||||
import { TimestampMillis } from '../utils/timestamp';
|
||||
import { ApiTokenDto, ApiTokenWithSecretDto } from './api-token.dto';
|
||||
import { ApiToken } from './api-token.entity';
|
||||
|
||||
export const AUTH_TOKEN_PREFIX = 'hd2';
|
||||
|
@ -54,19 +53,19 @@ export class ApiTokenService {
|
|||
createToken(
|
||||
user: User,
|
||||
identifier: string,
|
||||
userDefinedValidUntil: TimestampMillis | undefined,
|
||||
userDefinedValidUntil: Date | null,
|
||||
): [Omit<ApiToken, 'id' | 'createdAt'>, string] {
|
||||
const secret = bufferToBase64Url(randomBytes(64));
|
||||
const keyId = bufferToBase64Url(randomBytes(8));
|
||||
// More about the choice of SHA-512 in the dev docs
|
||||
const accessTokenHash = createHash('sha512').update(secret).digest('hex');
|
||||
// Tokens can only be valid for a maximum of 2 years
|
||||
const maximumTokenValidity =
|
||||
new Date().getTime() + 2 * 365 * 24 * 60 * 60 * 1000;
|
||||
const maximumTokenValidity = new Date();
|
||||
maximumTokenValidity.setTime(
|
||||
maximumTokenValidity.getTime() + 2 * 365 * 24 * 60 * 60 * 1000,
|
||||
);
|
||||
const isTokenLimitedToMaximumValidity =
|
||||
!userDefinedValidUntil ||
|
||||
userDefinedValidUntil === 0 ||
|
||||
userDefinedValidUntil > maximumTokenValidity;
|
||||
!userDefinedValidUntil || userDefinedValidUntil > maximumTokenValidity;
|
||||
const validUntil = isTokenLimitedToMaximumValidity
|
||||
? maximumTokenValidity
|
||||
: userDefinedValidUntil;
|
||||
|
@ -83,7 +82,7 @@ export class ApiTokenService {
|
|||
async addToken(
|
||||
user: User,
|
||||
identifier: string,
|
||||
validUntil: TimestampMillis | undefined,
|
||||
validUntil: Date | null,
|
||||
): Promise<ApiTokenWithSecretDto> {
|
||||
user.apiTokens = this.getTokensByUser(user);
|
||||
|
||||
|
@ -176,13 +175,13 @@ export class ApiTokenService {
|
|||
const tokenDto: ApiTokenDto = {
|
||||
label: authToken.label,
|
||||
keyId: authToken.keyId,
|
||||
createdAt: authToken.createdAt,
|
||||
validUntil: authToken.validUntil,
|
||||
createdAt: authToken.createdAt.toISOString(),
|
||||
validUntil: authToken.validUntil.toISOString(),
|
||||
lastUsedAt: null,
|
||||
};
|
||||
|
||||
if (authToken.lastUsedAt) {
|
||||
tokenDto.lastUsedAt = new Date(authToken.lastUsedAt);
|
||||
tokenDto.lastUsedAt = new Date(authToken.lastUsedAt).toISOString();
|
||||
}
|
||||
|
||||
return tokenDto;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -23,7 +23,12 @@ export class MockApiTokenGuard {
|
|||
try {
|
||||
this.user = await this.usersService.getUserByUsername('hardcoded');
|
||||
} catch (e) {
|
||||
this.user = await this.usersService.createUser('hardcoded', 'Testy');
|
||||
this.user = await this.usersService.createUser(
|
||||
'hardcoded',
|
||||
'Testy',
|
||||
null,
|
||||
null,
|
||||
);
|
||||
}
|
||||
}
|
||||
req.user = this.user;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue