enhancement(api-tokens): add prefix and more strict validation

Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
Erik Michelson 2024-03-23 01:14:41 +01:00
parent 0db5a0856b
commit 92bde4d281
3 changed files with 28 additions and 9 deletions

View file

@ -230,7 +230,7 @@ describe('AuthService', () => {
return authToken;
});
const userByToken = await service.validateToken(
`${authToken.keyId}.${testSecret}`,
`hd2.${authToken.keyId}.${testSecret}`,
);
expect(userByToken).toEqual({
...user,
@ -238,14 +238,31 @@ describe('AuthService', () => {
});
});
describe('fails:', () => {
it('the prefix is missing', async () => {
await expect(
service.validateToken(`${authToken.keyId}.${'a'.repeat(73)}`),
).rejects.toThrow(TokenNotValidError);
});
it('the prefix is wrong', async () => {
await expect(
service.validateToken(`hd1.${authToken.keyId}.${'a'.repeat(73)}`),
).rejects.toThrow(TokenNotValidError);
});
it('the secret is missing', async () => {
await expect(
service.validateToken(`${authToken.keyId}`),
service.validateToken(`hd2.${authToken.keyId}`),
).rejects.toThrow(TokenNotValidError);
});
it('the secret is too long', async () => {
await expect(
service.validateToken(`${authToken.keyId}.${'a'.repeat(73)}`),
service.validateToken(`hd2.${authToken.keyId}.${'a'.repeat(73)}`),
).rejects.toThrow(TokenNotValidError);
});
it('the token contains sections after the secret', async () => {
await expect(
service.validateToken(
`hd2.${authToken.keyId}.${'a'.repeat(73)}.extra`,
),
).rejects.toThrow(TokenNotValidError);
});
});
@ -296,7 +313,7 @@ describe('AuthService', () => {
(new Date().getTime() + 2 * 365 * 24 * 60 * 60 * 1000),
).toBeLessThanOrEqual(10000);
expect(token.lastUsedAt).toBeNull();
expect(token.secret.startsWith(token.keyId)).toBeTruthy();
expect(token.secret.startsWith('hd2.' + token.keyId)).toBeTruthy();
});
it('with validUntil not 0', async () => {
jest.spyOn(authTokenRepo, 'find').mockResolvedValueOnce([authToken]);
@ -313,7 +330,7 @@ describe('AuthService', () => {
expect(token.label).toEqual(identifier);
expect(token.validUntil.getTime()).toEqual(validUntil);
expect(token.lastUsedAt).toBeNull();
expect(token.secret.startsWith(token.keyId)).toBeTruthy();
expect(token.secret.startsWith('hd2.' + token.keyId)).toBeTruthy();
});
it('should throw TooManyTokensError when number of tokens >= 200', async () => {
jest

View file

@ -21,6 +21,8 @@ import { TimestampMillis } from '../utils/timestamp';
import { AuthTokenDto, AuthTokenWithSecretDto } from './auth-token.dto';
import { AuthToken } from './auth-token.entity';
export const AUTH_TOKEN_PREFIX = 'hd2';
@Injectable()
export class AuthService {
constructor(
@ -32,8 +34,8 @@ export class AuthService {
}
async validateToken(tokenString: string): Promise<User> {
const [keyId, secret] = tokenString.split('.');
if (!secret) {
const [prefix, keyId, secret, ...rest] = tokenString.split('.');
if (!keyId || !secret || prefix !== AUTH_TOKEN_PREFIX || rest.length > 0) {
throw new TokenNotValidError('Invalid AuthToken format');
}
if (secret.length != 86) {
@ -105,7 +107,7 @@ export class AuthService {
)) as AuthToken;
return this.toAuthTokenWithSecretDto(
createdToken,
`${createdToken.keyId}.${secret}`,
`${AUTH_TOKEN_PREFIX}.${createdToken.keyId}.${secret}`,
);
}

View file

@ -51,7 +51,7 @@ describe('Tokens', () => {
Date.now(),
);
expect(response.body.lastUsedAt).toBe(null);
expect(response.body.secret.length).toBe(98);
expect(response.body.secret.length).toBe(102);
});
it(`GET /tokens`, async () => {