add credentials login with secure cookies
This commit is contained in:
@@ -30,6 +30,7 @@
|
|||||||
"@types/jsonwebtoken": "^9.0.10",
|
"@types/jsonwebtoken": "^9.0.10",
|
||||||
"@types/passport-local": "^1.0.38",
|
"@types/passport-local": "^1.0.38",
|
||||||
"argon2": "^0.44.0",
|
"argon2": "^0.44.0",
|
||||||
|
"cookie-parser": "^1.4.7",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"dotenv": "^17.2.1",
|
"dotenv": "^17.2.1",
|
||||||
"ejs": "^3.1.10",
|
"ejs": "^3.1.10",
|
||||||
@@ -53,6 +54,7 @@
|
|||||||
"@nestjs/testing": "^11.0.1",
|
"@nestjs/testing": "^11.0.1",
|
||||||
"@swc/cli": "^0.6.0",
|
"@swc/cli": "^0.6.0",
|
||||||
"@swc/core": "^1.10.7",
|
"@swc/core": "^1.10.7",
|
||||||
|
"@types/cookie-parser": "^1.4.9",
|
||||||
"@types/ejs": "^3.1.5",
|
"@types/ejs": "^3.1.5",
|
||||||
"@types/express": "^5.0.0",
|
"@types/express": "^5.0.0",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
|
|||||||
@@ -1,16 +1,25 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { AuthRegistry } from './auth-registry';
|
import { AuthRegistry } from './auth-registry';
|
||||||
import * as dayjs from 'dayjs';
|
import * as dayjs from 'dayjs';
|
||||||
import { isDefined, ResetAuth } from '@my-monorepo/common';
|
import { AuthTokenPayload, isDefined, ResetAuth } from '@my-monorepo/common';
|
||||||
import { CryptoService } from '../../../services/core/crypto/crypto.service';
|
import { CryptoService } from '../../../services/core/crypto/crypto.service';
|
||||||
|
import { EnvService } from '../../../services/core/env/env.service';
|
||||||
|
|
||||||
const RESET_TOKEN_VALIDITY_MINUTES = 15;
|
const RESET_TOKEN_VALIDITY_MINUTES = 15;
|
||||||
|
|
||||||
|
const REFRESH_TOKEN_VALIDITY_DAYS = 7;
|
||||||
|
|
||||||
|
export interface TokenWithHmac {
|
||||||
|
token: string;
|
||||||
|
hmac: string;
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthCoreService {
|
export class AuthCoreService {
|
||||||
constructor(
|
constructor(
|
||||||
private authRegistry: AuthRegistry,
|
private authRegistry: AuthRegistry,
|
||||||
private cryptoService: CryptoService,
|
private cryptoService: CryptoService,
|
||||||
|
private envService: EnvService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async addNewResetToken(
|
async addNewResetToken(
|
||||||
@@ -31,4 +40,41 @@ export class AuthCoreService {
|
|||||||
const res = await this.authRegistry.checkAndInvalidateResetToken(resetAuth);
|
const res = await this.authRegistry.checkAndInvalidateResetToken(resetAuth);
|
||||||
return res > 0;
|
return res > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async addNewRefreshToken(userId: string): Promise<TokenWithHmac> {
|
||||||
|
const { token } = await this.authRegistry.addRefreshToken({
|
||||||
|
userId,
|
||||||
|
token: this.createUserAuthJwt({ userId }),
|
||||||
|
validUntil: dayjs().add(REFRESH_TOKEN_VALIDITY_DAYS, 'days'),
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO : add hash and return validity (req and in JWT)
|
||||||
|
return { token, hmac: this.generateAuthHmac(token) };
|
||||||
|
}
|
||||||
|
|
||||||
|
async verifyPassword(opts: {
|
||||||
|
hashedPassword: string;
|
||||||
|
plainTextPassword: string;
|
||||||
|
}): Promise<boolean> {
|
||||||
|
return this.cryptoService.verifyPassword(
|
||||||
|
opts.hashedPassword,
|
||||||
|
opts.plainTextPassword,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
createAccessToken(userId: string): TokenWithHmac {
|
||||||
|
const token = this.createUserAuthJwt({ userId });
|
||||||
|
return { token, hmac: this.generateAuthHmac(token) };
|
||||||
|
}
|
||||||
|
|
||||||
|
private createUserAuthJwt(payload: AuthTokenPayload): string {
|
||||||
|
return this.cryptoService.createJwt(payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateAuthHmac(payload: string): string {
|
||||||
|
return this.cryptoService.generateHmac(
|
||||||
|
payload,
|
||||||
|
this.envService.config.secrets.authHmac,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,22 @@ export class AuthRegistry {
|
|||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async addRefreshToken(opts: {
|
||||||
|
userId: string;
|
||||||
|
token: string;
|
||||||
|
validUntil: dayjs.Dayjs;
|
||||||
|
}): Promise<{ token: string; user_id: string }> {
|
||||||
|
return this.database
|
||||||
|
.insertInto('refresh_tokens')
|
||||||
|
.values({
|
||||||
|
user_id: opts.userId,
|
||||||
|
token: opts.token,
|
||||||
|
valid_until: opts.validUntil.toDate(),
|
||||||
|
})
|
||||||
|
.returning(['user_id', 'token'])
|
||||||
|
.executeTakeFirstOrThrow();
|
||||||
|
}
|
||||||
|
|
||||||
async checkAndInvalidateResetToken(resetAuth: ResetAuth): Promise<bigint> {
|
async checkAndInvalidateResetToken(resetAuth: ResetAuth): Promise<bigint> {
|
||||||
const res = await this.database
|
const res = await this.database
|
||||||
.deleteFrom('reset_tokens')
|
.deleteFrom('reset_tokens')
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
|
import { CredentialAuth, ResetAuth } from '@my-monorepo/common';
|
||||||
import {
|
import {
|
||||||
Injectable,
|
Injectable,
|
||||||
NotFoundException,
|
NotFoundException,
|
||||||
UnauthorizedException,
|
UnauthorizedException,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { UsersCoreService } from '../../users/users-core/users-core.service';
|
|
||||||
import { AuthCoreService } from '../auth-core/auth-core.service';
|
|
||||||
import { ResetAuth } from '@my-monorepo/common';
|
|
||||||
import { CryptoService } from '../../../services/core/crypto/crypto.service';
|
import { CryptoService } from '../../../services/core/crypto/crypto.service';
|
||||||
|
import { UsersCoreService } from '../../users/users-core/users-core.service';
|
||||||
|
import { AuthCoreService, TokenWithHmac } from '../auth-core/auth-core.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthControllerService {
|
export class AuthControllerService {
|
||||||
@@ -16,11 +16,30 @@ export class AuthControllerService {
|
|||||||
private cryptoService: CryptoService,
|
private cryptoService: CryptoService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async verifyCredentials() {
|
async handleCredentialsAuth(credentials: CredentialAuth): Promise<{
|
||||||
const isValidCredential = await this.userCoreService.existsEmailPassword();
|
refreshToken: TokenWithHmac;
|
||||||
if (!isValidCredential) {
|
accessToken: TokenWithHmac;
|
||||||
|
}> {
|
||||||
|
const userWithHash =
|
||||||
|
await this.userCoreService.getUserIdAndHashPass(credentials);
|
||||||
|
if (!userWithHash || !userWithHash.hash) {
|
||||||
throw new UnauthorizedException('invalid credentials');
|
throw new UnauthorizedException('invalid credentials');
|
||||||
}
|
}
|
||||||
|
const { userId, hash } = userWithHash;
|
||||||
|
const isPasswordValid = await this.authCoreService.verifyPassword({
|
||||||
|
hashedPassword: hash,
|
||||||
|
plainTextPassword: credentials.password,
|
||||||
|
});
|
||||||
|
if (!isPasswordValid) {
|
||||||
|
throw new UnauthorizedException('invalid credentials');
|
||||||
|
}
|
||||||
|
|
||||||
|
const refreshToken = await this.authCoreService.addNewRefreshToken(userId);
|
||||||
|
const accessToken = this.authCoreService.createAccessToken(userId);
|
||||||
|
return {
|
||||||
|
refreshToken,
|
||||||
|
accessToken,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async resetPassword(resetData: ResetAuth) {
|
async resetPassword(resetData: ResetAuth) {
|
||||||
|
|||||||
@@ -1,15 +1,66 @@
|
|||||||
import { Body, Controller, Post } from '@nestjs/common';
|
import { Body, Controller, Post, Res } from '@nestjs/common';
|
||||||
import { AuthControllerService } from './auth-controller.service';
|
import { AuthControllerService } from './auth-controller.service';
|
||||||
import { ZodValidationPipe } from '../../../lib/nestjs/zod-validation/zod-validation.pipe';
|
import { ZodValidationPipe } from '../../../lib/nestjs/zod-validation/zod-validation.pipe';
|
||||||
import { ResetAuth, resetAuthParser } from '@my-monorepo/common';
|
import {
|
||||||
|
CredentialAuth,
|
||||||
|
credentialAuthParser,
|
||||||
|
ResetAuth,
|
||||||
|
resetAuthParser,
|
||||||
|
} from '@my-monorepo/common';
|
||||||
|
import { Response } from 'express';
|
||||||
|
import { EnvService, EnvType } from '../../../services/core/env/env.service';
|
||||||
|
|
||||||
|
const REFRESH_TOKEN_COOKIE_NAME = 'refresh';
|
||||||
|
const REFRESH_HASH_COOKIE_NAME = `${REFRESH_TOKEN_COOKIE_NAME}_hash`;
|
||||||
|
const ACCESS_TOKEN_COOKIE_NAME = 'access';
|
||||||
|
const ACCESS_HASH_COOKIE_NAME = `${ACCESS_TOKEN_COOKIE_NAME}_hash`;
|
||||||
|
|
||||||
@Controller('auth')
|
@Controller('auth')
|
||||||
export class AuthController {
|
export class AuthController {
|
||||||
constructor(private authService: AuthControllerService) {}
|
constructor(
|
||||||
|
private authService: AuthControllerService,
|
||||||
|
private envService: EnvService,
|
||||||
|
) {}
|
||||||
|
|
||||||
@Post('credentials')
|
@Post('credentials')
|
||||||
async postCredentials() {
|
async postCredentials(
|
||||||
return this.authService.verifyCredentials();
|
@Body(new ZodValidationPipe(credentialAuthParser))
|
||||||
|
credentials: CredentialAuth,
|
||||||
|
@Res({ passthrough: true }) response: Response,
|
||||||
|
) {
|
||||||
|
const { accessToken, refreshToken } =
|
||||||
|
await this.authService.handleCredentialsAuth(credentials);
|
||||||
|
|
||||||
|
console.log('>>> paul-debug', {
|
||||||
|
httpOnly: true,
|
||||||
|
sameSite: 'strict',
|
||||||
|
domain: this.envService.config.app.domain,
|
||||||
|
secure: this.envService.config.app.envType === EnvType.Production,
|
||||||
|
});
|
||||||
|
response.cookie(REFRESH_TOKEN_COOKIE_NAME, refreshToken.token, {
|
||||||
|
httpOnly: true,
|
||||||
|
sameSite: 'strict',
|
||||||
|
domain: this.envService.config.app.domain,
|
||||||
|
secure: this.envService.config.app.envType === EnvType.Production,
|
||||||
|
});
|
||||||
|
response.cookie(REFRESH_HASH_COOKIE_NAME, refreshToken.hmac, {
|
||||||
|
httpOnly: false,
|
||||||
|
sameSite: 'strict',
|
||||||
|
domain: this.envService.config.app.domain,
|
||||||
|
secure: this.envService.config.app.envType === EnvType.Production,
|
||||||
|
});
|
||||||
|
response.cookie(ACCESS_TOKEN_COOKIE_NAME, accessToken.token, {
|
||||||
|
httpOnly: true,
|
||||||
|
sameSite: 'strict',
|
||||||
|
domain: this.envService.config.app.domain,
|
||||||
|
secure: this.envService.config.app.envType === EnvType.Production,
|
||||||
|
});
|
||||||
|
response.cookie(ACCESS_HASH_COOKIE_NAME, accessToken.hmac, {
|
||||||
|
httpOnly: false,
|
||||||
|
sameSite: 'strict',
|
||||||
|
domain: this.envService.config.app.domain,
|
||||||
|
secure: this.envService.config.app.envType === EnvType.Production,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('reset')
|
@Post('reset')
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { UsersRegistry } from './users-registry';
|
import { UsersRegistry } from './users-registry';
|
||||||
import { CreateUser, Result, User } from '@my-monorepo/common';
|
import { CreateUser, CredentialAuth, Result, User } from '@my-monorepo/common';
|
||||||
import { UserEmailAlreadyExistError } from './users-error';
|
import { UserEmailAlreadyExistError } from './users-error';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UsersCoreService {
|
export class UsersCoreService {
|
||||||
constructor(private userRegistry: UsersRegistry) {}
|
constructor(private userRegistry: UsersRegistry) {}
|
||||||
|
|
||||||
async existsEmailPassword() {
|
async getUserIdAndHashPass(
|
||||||
return await this.userRegistry.existsEmailPassword('', '');
|
credentials: CredentialAuth,
|
||||||
|
): Promise<{ userId: string; hash?: string } | undefined> {
|
||||||
|
return await this.userRegistry.getUserIdPasswordFromEmail(
|
||||||
|
credentials.email,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async addUser(
|
async addUser(
|
||||||
|
|||||||
@@ -11,16 +11,22 @@ export class UsersRegistry {
|
|||||||
|
|
||||||
constructor(private databaseService: DatabaseService) {}
|
constructor(private databaseService: DatabaseService) {}
|
||||||
|
|
||||||
async existsEmailPassword(email: string, hashedPassword: string) {
|
async getUserIdPasswordFromEmail(
|
||||||
const id = await this.database
|
email: string,
|
||||||
|
): Promise<{ userId: string; hash?: string } | undefined> {
|
||||||
|
const user = await this.database
|
||||||
.selectFrom('users')
|
.selectFrom('users')
|
||||||
.select('id')
|
.select(['id', 'hash'])
|
||||||
.where((eb) => eb('email', '=', email).and('hash', '=', hashedPassword))
|
.where((eb) => eb('email', '=', email))
|
||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
|
|
||||||
return isDefined(id);
|
return isDefined(user)
|
||||||
|
? {
|
||||||
|
userId: user.id,
|
||||||
|
hash: user.hash ?? undefined,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
async addUser(
|
async addUser(
|
||||||
newUser: CreateUser,
|
newUser: CreateUser,
|
||||||
): Promise<Result<User, UserEmailAlreadyExistError>> {
|
): Promise<Result<User, UserEmailAlreadyExistError>> {
|
||||||
|
|||||||
11
apps/backend/src/database/db.d.ts
vendored
11
apps/backend/src/database/db.d.ts
vendored
@@ -13,6 +13,16 @@ export type Int8 = ColumnType<string, bigint | number | string, bigint | number
|
|||||||
|
|
||||||
export type Timestamp = ColumnType<Date, Date | string, Date | string>;
|
export type Timestamp = ColumnType<Date, Date | string, Date | string>;
|
||||||
|
|
||||||
|
export interface RefreshTokens {
|
||||||
|
created_at: Generated<Timestamp>;
|
||||||
|
deleted_at: Timestamp | null;
|
||||||
|
id: Generated<Int8>;
|
||||||
|
token: string;
|
||||||
|
updated_at: Generated<Timestamp>;
|
||||||
|
user_id: Generated<Int8>;
|
||||||
|
valid_until: Timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ResetTokens {
|
export interface ResetTokens {
|
||||||
created_at: Generated<Timestamp>;
|
created_at: Generated<Timestamp>;
|
||||||
deleted_at: Timestamp | null;
|
deleted_at: Timestamp | null;
|
||||||
@@ -35,6 +45,7 @@ export interface Users {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface DB {
|
export interface DB {
|
||||||
|
refresh_tokens: RefreshTokens;
|
||||||
reset_tokens: ResetTokens;
|
reset_tokens: ResetTokens;
|
||||||
users: Users;
|
users: Users;
|
||||||
}
|
}
|
||||||
|
|||||||
11
apps/backend/src/lib/nestjs/cookies/cookies-decorator.ts
Normal file
11
apps/backend/src/lib/nestjs/cookies/cookies-decorator.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
|
||||||
|
import { Request } from 'express';
|
||||||
|
import { ZodType } from 'zod';
|
||||||
|
|
||||||
|
export const Cookies = createParamDecorator(
|
||||||
|
<T>(data: { key: string; parser: ZodType<T> }, ctx: ExecutionContext) => {
|
||||||
|
const request = ctx.switchToHttp().getRequest<Request>();
|
||||||
|
const res = data.parser.safeParse(request.cookies?.[data.key]);
|
||||||
|
return res.success ? res.data : undefined;
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
import { AppErrorFilter } from './app-error.filter';
|
import { AppErrorFilter } from './app-error.filter';
|
||||||
|
import * as cookieParser from 'cookie-parser';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
app.useGlobalFilters(new AppErrorFilter());
|
app.useGlobalFilters(new AppErrorFilter());
|
||||||
|
app.use(cookieParser());
|
||||||
|
console.log('Starting server on port', process.env.PORT ?? 3000);
|
||||||
await app.listen(process.env.PORT ?? 3000);
|
await app.listen(process.env.PORT ?? 3000);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|||||||
@@ -38,4 +38,12 @@ export class CryptoService {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
generateHmac(payload: string, key: string) {
|
||||||
|
return crypto.createHmac('sha256', key).update(payload).digest('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
validateHmac(payload: string, hmac: string, key: string): boolean {
|
||||||
|
return this.generateHmac(payload, key) === hmac;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,11 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import z from 'zod';
|
import z from 'zod';
|
||||||
import * as dotenv from 'dotenv';
|
import * as dotenv from 'dotenv';
|
||||||
|
|
||||||
|
export enum EnvType {
|
||||||
|
Dev = 'dev',
|
||||||
|
Production = 'production',
|
||||||
|
}
|
||||||
|
|
||||||
const envParser = z
|
const envParser = z
|
||||||
.object({
|
.object({
|
||||||
DATABASE: z.string(),
|
DATABASE: z.string(),
|
||||||
@@ -13,8 +18,10 @@ const envParser = z
|
|||||||
MAIL_PORT: z.coerce.number(),
|
MAIL_PORT: z.coerce.number(),
|
||||||
MAIL_USER: z.string().email(),
|
MAIL_USER: z.string().email(),
|
||||||
MAIL_PASSWORD: z.string(),
|
MAIL_PASSWORD: z.string(),
|
||||||
APP_FRONTEND_URL: z.string(),
|
APP_DOMAIN: z.string(),
|
||||||
|
APP_ENV_TYPE: z.nativeEnum(EnvType),
|
||||||
SECRET_AUTH_SIGN: z.string(),
|
SECRET_AUTH_SIGN: z.string(),
|
||||||
|
SECRET_AUTH_HMAC: z.string(),
|
||||||
})
|
})
|
||||||
.transform((parsed) => ({
|
.transform((parsed) => ({
|
||||||
database: {
|
database: {
|
||||||
@@ -31,10 +38,12 @@ const envParser = z
|
|||||||
password: parsed.MAIL_PASSWORD,
|
password: parsed.MAIL_PASSWORD,
|
||||||
},
|
},
|
||||||
app: {
|
app: {
|
||||||
frontendUrl: parsed.APP_FRONTEND_URL,
|
domain: parsed.APP_DOMAIN,
|
||||||
|
envType: parsed.APP_ENV_TYPE,
|
||||||
},
|
},
|
||||||
secrets: {
|
secrets: {
|
||||||
authSign: parsed.SECRET_AUTH_SIGN,
|
authSign: parsed.SECRET_AUTH_SIGN,
|
||||||
|
authHmac: parsed.SECRET_AUTH_HMAC,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export class LinkBuilderService {
|
|||||||
path: string[],
|
path: string[],
|
||||||
queryParams: Record<string, string> = {},
|
queryParams: Record<string, string> = {},
|
||||||
) {
|
) {
|
||||||
const base = this.envService.config.app.frontendUrl;
|
const base = this.envService.config.app.domain;
|
||||||
const segments = path.join('/');
|
const segments = path.join('/');
|
||||||
const queryParamsList = Object.entries(queryParams);
|
const queryParamsList = Object.entries(queryParams);
|
||||||
const joinedQueryParam = queryParamsList
|
const joinedQueryParam = queryParamsList
|
||||||
|
|||||||
@@ -6,10 +6,17 @@ meta {
|
|||||||
|
|
||||||
post {
|
post {
|
||||||
url: {{BASE_URL}}/auth/credentials
|
url: {{BASE_URL}}/auth/credentials
|
||||||
body: none
|
body: json
|
||||||
auth: inherit
|
auth: inherit
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body:json {
|
||||||
|
{
|
||||||
|
"email": "paul@cowsi.ch",
|
||||||
|
"password": "Password1234"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
settings {
|
settings {
|
||||||
encodeUrl: true
|
encodeUrl: true
|
||||||
}
|
}
|
||||||
|
|||||||
3
packages/common/src/models/auth/auth-token-payload.ts
Normal file
3
packages/common/src/models/auth/auth-token-payload.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export interface AuthTokenPayload {
|
||||||
|
userId: string;
|
||||||
|
}
|
||||||
@@ -5,10 +5,4 @@ export const credentialAuthParser = z.object({
|
|||||||
password: z.string({ message: "invalid password" }),
|
password: z.string({ message: "invalid password" }),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const credentialAuthResponse = z.object({
|
|
||||||
accessToken: z.string({ message: "invalid access tokens" }),
|
|
||||||
});
|
|
||||||
|
|
||||||
export type CredentialAuth = z.infer<typeof credentialAuthParser>;
|
export type CredentialAuth = z.infer<typeof credentialAuthParser>;
|
||||||
|
|
||||||
export type CredentialAuthResponse = z.infer<typeof credentialAuthResponse>;
|
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
|
export * from "./auth-token-payload";
|
||||||
export * from "./credential-auth";
|
export * from "./credential-auth";
|
||||||
export * from "./reset-auth";
|
export * from "./reset-auth";
|
||||||
|
|||||||
29
pnpm-lock.yaml
generated
29
pnpm-lock.yaml
generated
@@ -34,6 +34,9 @@ importers:
|
|||||||
argon2:
|
argon2:
|
||||||
specifier: ^0.44.0
|
specifier: ^0.44.0
|
||||||
version: 0.44.0
|
version: 0.44.0
|
||||||
|
cookie-parser:
|
||||||
|
specifier: ^1.4.7
|
||||||
|
version: 1.4.7
|
||||||
dayjs:
|
dayjs:
|
||||||
specifier: ^1.11.13
|
specifier: ^1.11.13
|
||||||
version: 1.11.13
|
version: 1.11.13
|
||||||
@@ -98,6 +101,9 @@ importers:
|
|||||||
'@swc/core':
|
'@swc/core':
|
||||||
specifier: ^1.10.7
|
specifier: ^1.10.7
|
||||||
version: 1.12.11
|
version: 1.12.11
|
||||||
|
'@types/cookie-parser':
|
||||||
|
specifier: ^1.4.9
|
||||||
|
version: 1.4.9(@types/express@5.0.3)
|
||||||
'@types/ejs':
|
'@types/ejs':
|
||||||
specifier: ^3.1.5
|
specifier: ^3.1.5
|
||||||
version: 3.1.5
|
version: 3.1.5
|
||||||
@@ -1042,6 +1048,11 @@ packages:
|
|||||||
'@types/connect@3.4.38':
|
'@types/connect@3.4.38':
|
||||||
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
|
resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==}
|
||||||
|
|
||||||
|
'@types/cookie-parser@1.4.9':
|
||||||
|
resolution: {integrity: sha512-tGZiZ2Gtc4m3wIdLkZ8mkj1T6CEHb35+VApbL2T14Dew8HA7c+04dmKqsKRNC+8RJPm16JEK0tFSwdZqubfc4g==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/express': '*'
|
||||||
|
|
||||||
'@types/cookiejar@2.1.5':
|
'@types/cookiejar@2.1.5':
|
||||||
resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==}
|
resolution: {integrity: sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==}
|
||||||
|
|
||||||
@@ -1666,6 +1677,13 @@ packages:
|
|||||||
convert-source-map@2.0.0:
|
convert-source-map@2.0.0:
|
||||||
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
|
||||||
|
|
||||||
|
cookie-parser@1.4.7:
|
||||||
|
resolution: {integrity: sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==}
|
||||||
|
engines: {node: '>= 0.8.0'}
|
||||||
|
|
||||||
|
cookie-signature@1.0.6:
|
||||||
|
resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==}
|
||||||
|
|
||||||
cookie-signature@1.2.2:
|
cookie-signature@1.2.2:
|
||||||
resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==}
|
resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==}
|
||||||
engines: {node: '>=6.6.0'}
|
engines: {node: '>=6.6.0'}
|
||||||
@@ -4860,6 +4878,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@types/node': 22.16.3
|
'@types/node': 22.16.3
|
||||||
|
|
||||||
|
'@types/cookie-parser@1.4.9(@types/express@5.0.3)':
|
||||||
|
dependencies:
|
||||||
|
'@types/express': 5.0.3
|
||||||
|
|
||||||
'@types/cookiejar@2.1.5': {}
|
'@types/cookiejar@2.1.5': {}
|
||||||
|
|
||||||
'@types/ejs@3.1.5': {}
|
'@types/ejs@3.1.5': {}
|
||||||
@@ -5650,6 +5672,13 @@ snapshots:
|
|||||||
|
|
||||||
convert-source-map@2.0.0: {}
|
convert-source-map@2.0.0: {}
|
||||||
|
|
||||||
|
cookie-parser@1.4.7:
|
||||||
|
dependencies:
|
||||||
|
cookie: 0.7.2
|
||||||
|
cookie-signature: 1.0.6
|
||||||
|
|
||||||
|
cookie-signature@1.0.6: {}
|
||||||
|
|
||||||
cookie-signature@1.2.2: {}
|
cookie-signature@1.2.2: {}
|
||||||
|
|
||||||
cookie@0.7.2: {}
|
cookie@0.7.2: {}
|
||||||
|
|||||||
Reference in New Issue
Block a user