mirror of
https://github.com/BradNut/boredgame
synced 2025-09-08 17:40:22 +00:00
Creating controllers, updating deps, and adding more repositories.
This commit is contained in:
parent
16191509b4
commit
3190e9601e
16 changed files with 799 additions and 539 deletions
26
package.json
26
package.json
|
|
@ -25,17 +25,17 @@
|
|||
"devDependencies": {
|
||||
"@melt-ui/pp": "^0.3.2",
|
||||
"@melt-ui/svelte": "^0.83.0",
|
||||
"@playwright/test": "^1.45.2",
|
||||
"@playwright/test": "^1.45.3",
|
||||
"@resvg/resvg-js": "^2.6.2",
|
||||
"@sveltejs/adapter-auto": "^3.2.2",
|
||||
"@sveltejs/enhanced-img": "^0.3.0",
|
||||
"@sveltejs/enhanced-img": "^0.3.1",
|
||||
"@sveltejs/kit": "^2.5.18",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.1.1",
|
||||
"@types/cookie": "^0.6.0",
|
||||
"@types/node": "^20.14.11",
|
||||
"@types/node": "^20.14.13",
|
||||
"@types/pg": "^8.11.6",
|
||||
"@typescript-eslint/eslint-plugin": "^7.16.1",
|
||||
"@typescript-eslint/parser": "^7.16.1",
|
||||
"@typescript-eslint/eslint-plugin": "^7.17.0",
|
||||
"@typescript-eslint/parser": "^7.17.0",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"drizzle-kit": "^0.23.0",
|
||||
"eslint": "^8.57.0",
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
"eslint-plugin-svelte": "^2.43.0",
|
||||
"just-clone": "^6.2.0",
|
||||
"just-debounce-it": "^3.2.0",
|
||||
"postcss": "^8.4.39",
|
||||
"postcss": "^8.4.40",
|
||||
"postcss-import": "^16.1.0",
|
||||
"postcss-load-config": "^5.1.0",
|
||||
"postcss-preset-env": "^9.6.0",
|
||||
|
|
@ -61,12 +61,12 @@
|
|||
"sveltekit-flash-message": "^2.4.4",
|
||||
"sveltekit-rate-limiter": "^0.5.2",
|
||||
"sveltekit-superforms": "^2.16.1",
|
||||
"tailwindcss": "^3.4.6",
|
||||
"tailwindcss": "^3.4.7",
|
||||
"ts-node": "^10.9.2",
|
||||
"tslib": "^2.6.3",
|
||||
"tsx": "^4.16.2",
|
||||
"typescript": "^5.5.3",
|
||||
"vite": "^5.3.4",
|
||||
"typescript": "^5.5.4",
|
||||
"vite": "^5.3.5",
|
||||
"vitest": "^1.6.0",
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
|
|
@ -80,7 +80,7 @@
|
|||
"@hono/zod-validator": "^0.2.2",
|
||||
"@iconify-icons/line-md": "^1.2.30",
|
||||
"@iconify-icons/mdi": "^1.2.48",
|
||||
"@internationalized/date": "^3.5.4",
|
||||
"@internationalized/date": "^3.5.5",
|
||||
"@lucia-auth/adapter-drizzle": "^1.0.7",
|
||||
"@lukeed/uuid": "^2.0.1",
|
||||
"@neondatabase/serverless": "^0.9.4",
|
||||
|
|
@ -96,10 +96,10 @@
|
|||
"cookie": "^0.6.0",
|
||||
"dotenv": "^16.4.5",
|
||||
"dotenv-expand": "^11.0.6",
|
||||
"drizzle-orm": "^0.32.0",
|
||||
"drizzle-orm": "^0.32.1",
|
||||
"feather-icons": "^4.29.2",
|
||||
"formsnap": "^1.0.1",
|
||||
"hono": "^4.5.0",
|
||||
"hono": "^4.5.2",
|
||||
"hono-rate-limiter": "^0.4.0",
|
||||
"html-entities": "^2.5.2",
|
||||
"iconify-icon": "^2.1.0",
|
||||
|
|
@ -122,6 +122,6 @@
|
|||
"tailwind-merge": "^2.4.0",
|
||||
"tailwind-variants": "^0.2.1",
|
||||
"tailwindcss-animate": "^1.0.7",
|
||||
"zod-to-json-schema": "^3.23.1"
|
||||
"zod-to-json-schema": "^3.23.2"
|
||||
}
|
||||
}
|
||||
900
pnpm-lock.yaml
900
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
|
@ -4,7 +4,7 @@ import { requireAuth } from "../middleware/auth.middleware";
|
|||
import { registerEmailPasswordDto } from '$lib/dtos/register-emailpassword.dto';
|
||||
import { limiter } from '../middleware/rate-limiter.middleware';
|
||||
|
||||
const users = new Hono()
|
||||
const app = new Hono()
|
||||
.get('/me', requireAuth, async (c) => {
|
||||
const user = c.var.user;
|
||||
return c.json({ user });
|
||||
|
|
@ -19,5 +19,4 @@ const users = new Hono()
|
|||
return c.json({ message: 'Verification email sent' });
|
||||
});
|
||||
|
||||
export default users;
|
||||
export type UsersType = typeof users
|
||||
export default app;
|
||||
|
|
|
|||
13
src/lib/server/api/controllers/login.controller.ts
Normal file
13
src/lib/server/api/controllers/login.controller.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { Hono } from 'hono';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { registerEmailPasswordDto } from '$lib/dtos/register-emailpassword.dto';
|
||||
import { limiter } from '../middleware/rate-limiter.middleware';
|
||||
|
||||
const app = new Hono()
|
||||
.post('/', zValidator('json', registerEmailPasswordDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
|
||||
const { email } = c.req.valid('json');
|
||||
await loginRequestsService.create({ email });
|
||||
return c.json({ message: 'Verification email sent' });
|
||||
});
|
||||
|
||||
export default app;
|
||||
0
src/lib/server/api/controllers/signup.controller.ts
Normal file
0
src/lib/server/api/controllers/signup.controller.ts
Normal file
22
src/lib/server/api/controllers/user.controller.ts
Normal file
22
src/lib/server/api/controllers/user.controller.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { Hono } from 'hono';
|
||||
import { zValidator } from '@hono/zod-validator';
|
||||
import { requireAuth } from "../middleware/auth.middleware";
|
||||
import { registerEmailPasswordDto } from '$lib/dtos/register-emailpassword.dto';
|
||||
import { limiter } from '../middleware/rate-limiter.middleware';
|
||||
|
||||
const app = new Hono()
|
||||
.get('/me', requireAuth, async (c) => {
|
||||
const user = c.var.user;
|
||||
return c.json({ user });
|
||||
})
|
||||
.get('/user', requireAuth, async (c) => {
|
||||
const user = c.var.user;
|
||||
return c.json({ user });
|
||||
})
|
||||
.post('/login/request', zValidator('json', registerEmailPasswordDto), limiter({ limit: 10, minutes: 60 }), async (c) => {
|
||||
const { email } = c.req.valid('json');
|
||||
await this.loginRequestsService.create({ email });
|
||||
return c.json({ message: 'Verification email sent' });
|
||||
});
|
||||
|
||||
export default app;
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
import { Hono } from 'hono';
|
||||
import { hc } from 'hono/client';
|
||||
import { cors } from 'hono/cors';
|
||||
import { logger } from 'hono/logger';
|
||||
import { validateAuthSession, verifyOrigin } from './middleware/auth.middleware';
|
||||
import users from './controllers/iam.controller';
|
||||
import users from './controllers/user.controller';
|
||||
import { config } from './common/config';
|
||||
|
||||
/* ----------------------------------- Api ---------------------------------- */
|
||||
|
|
@ -9,10 +11,27 @@ const app = new Hono().basePath('/api');
|
|||
|
||||
/* --------------------------- Global Middlewares --------------------------- */
|
||||
app.use(verifyOrigin).use(validateAuthSession);
|
||||
app.use(logger());
|
||||
|
||||
app.use(
|
||||
'/*',
|
||||
cors({
|
||||
origin: [
|
||||
'http://localhost:5173',
|
||||
'http://localhost:80',
|
||||
'http://host.docker.internal:80',
|
||||
'http://host.docker.internal:5173'
|
||||
], // Replace with your allowed domains
|
||||
|
||||
allowMethods: ['POST'],
|
||||
allowHeaders: ['Content-Type']
|
||||
// credentials: true, // If you need to send cookies or HTTP authentication
|
||||
})
|
||||
);
|
||||
|
||||
/* --------------------------------- Routes --------------------------------- */
|
||||
const routes = app
|
||||
.route('/iam', users)
|
||||
.route('/user', users)
|
||||
.get('/', (c) => c.json({ message: 'Server is healthy' }));
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { type InferSelectModel, relations } from 'drizzle-orm';
|
|||
import { timestamps } from '../utils';
|
||||
import user_roles from './userRoles';
|
||||
|
||||
const usersTable = pgTable('users', {
|
||||
export const usersTable = pgTable('users', {
|
||||
id: uuid('id').primaryKey().defaultRandom(),
|
||||
cuid: text('cuid')
|
||||
.unique()
|
||||
|
|
@ -25,5 +25,3 @@ export const userRelations = relations(usersTable, ({ many }) => ({
|
|||
}));
|
||||
|
||||
export type Users = InferSelectModel<typeof usersTable>;
|
||||
|
||||
export default usersTable;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import { container } from 'tsyringe';
|
||||
import { lucia } from '../infrastructure/auth/lucia';
|
||||
// import { lucia } from '../infrastructure/auth/lucia';
|
||||
|
||||
// Symbol
|
||||
export const LuciaProvider = Symbol('LUCIA_PROVIDER');
|
||||
// // Symbol
|
||||
// export const LuciaProvider = Symbol('LUCIA_PROVIDER');
|
||||
|
||||
// Type
|
||||
export type LuciaProvider = typeof lucia;
|
||||
// // Type
|
||||
// export type LuciaProvider = typeof lucia;
|
||||
|
||||
// Register
|
||||
container.register<LuciaProvider>(LuciaProvider, { useValue: lucia });
|
||||
// // Register
|
||||
// container.register<LuciaProvider>(LuciaProvider, { useValue: lucia });
|
||||
|
|
|
|||
56
src/lib/server/api/repositories/users.repository.ts
Normal file
56
src/lib/server/api/repositories/users.repository.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import { eq, type InferInsertModel } from 'drizzle-orm';
|
||||
import { usersTable } from '../infrastructure/database/tables/users.table';
|
||||
import { takeFirstOrThrow } from '../infrastructure/database/utils';
|
||||
import { db } from '../infrastructure/database';
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Repository */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* ---------------------------------- About --------------------------------- */
|
||||
/*
|
||||
Repositories are the layer that interacts with the database. They are responsible for retrieving and
|
||||
storing data. They should not contain any business logic, only database queries.
|
||||
*/
|
||||
/* ---------------------------------- Notes --------------------------------- */
|
||||
/*
|
||||
Repositories should only contain methods for CRUD operations and any other database interactions.
|
||||
Any complex logic should be delegated to a service. If a repository method requires a transaction,
|
||||
it should be passed in as an argument or the class should have a method to set the transaction.
|
||||
In our case the method 'trxHost' is used to set the transaction context.
|
||||
*/
|
||||
|
||||
export type CreateUser = InferInsertModel<typeof usersTable>;
|
||||
export type UpdateUser = Partial<CreateUser>;
|
||||
|
||||
export class UsersRepository {
|
||||
async findOneById(id: string) {
|
||||
return db.query.usersTable.findFirst({
|
||||
where: eq(usersTable.id, id)
|
||||
});
|
||||
}
|
||||
|
||||
async findOneByIdOrThrow(id: string) {
|
||||
const user = await this.findOneById(id);
|
||||
if (!user) throw Error('User not found');
|
||||
return user;
|
||||
}
|
||||
|
||||
async findOneByEmail(email: string) {
|
||||
return db.query.usersTable.findFirst({
|
||||
where: eq(usersTable.email, email)
|
||||
});
|
||||
}
|
||||
|
||||
async create(data: CreateUser) {
|
||||
return db.insert(usersTable).values(data).returning().then(takeFirstOrThrow);
|
||||
}
|
||||
|
||||
async update(id: string, data: UpdateUser) {
|
||||
return db
|
||||
.update(usersTable)
|
||||
.set(data)
|
||||
.where(eq(usersTable.id, id))
|
||||
.returning()
|
||||
.then(takeFirstOrThrow);
|
||||
}
|
||||
}
|
||||
32
src/lib/server/api/services/hashing.service.ts
Normal file
32
src/lib/server/api/services/hashing.service.ts
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
import { Argon2id } from "oslo/password";
|
||||
|
||||
/* ---------------------------------- Note ---------------------------------- */
|
||||
/*
|
||||
Reference: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id
|
||||
|
||||
I use Scrpt as the hashing algorithm due to its higher compatability
|
||||
with vite's build system and it uses less memory than Argon2id.
|
||||
|
||||
You can use Argon2id or any other hashing algorithm you prefer.
|
||||
*/
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/*
|
||||
With Argon2id, you get the following error at times when vite optimizes its dependencies at times,
|
||||
|
||||
Error: Build failed with 2 errors:
|
||||
node_modules/.pnpm/@node-rs+argon2@1.7.0/node_modules/@node-rs/argon2/index.js:159:36: ERROR: No loader is configured for ".node" files: node_module
|
||||
*/
|
||||
/* -------------------------------------------------------------------------- */
|
||||
// If you don't use a hasher from oslo, which are preconfigured with recommended parameters from OWASP,
|
||||
// ensure that you configure them properly.
|
||||
export class HashingService {
|
||||
private readonly hasher = new Argon2id();
|
||||
|
||||
async hash(data: string) {
|
||||
return this.hasher.hash(data);
|
||||
}
|
||||
|
||||
async verify(hash: string, data: string) {
|
||||
return this.hasher.verify(hash, data)
|
||||
}
|
||||
}
|
||||
24
src/lib/server/api/services/iam.service.ts
Normal file
24
src/lib/server/api/services/iam.service.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import { lucia } from '../infrastructure/auth/lucia';
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Service */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* ---------------------------------- About --------------------------------- */
|
||||
/*
|
||||
Services are responsible for handling business logic and data manipulation.
|
||||
They genreally call on repositories or other services to complete a use-case.
|
||||
*/
|
||||
/* ---------------------------------- Notes --------------------------------- */
|
||||
/*
|
||||
Services should be kept as clean and simple as possible.
|
||||
|
||||
Create private functions to handle complex logic and keep the public methods as
|
||||
simple as possible. This makes the service easier to read, test and understand.
|
||||
*/
|
||||
/* -------------------------------------------------------------------------- */
|
||||
export class IamService {
|
||||
async logout(sessionId: string) {
|
||||
return lucia.invalidateSession(sessionId);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
import { BadRequest } from '../common/errors';
|
||||
import { DatabaseProvider } from '../providers';
|
||||
import { MailerService } from './mailer.service';
|
||||
import { TokensService } from './tokens.service';
|
||||
import { LuciaProvider } from '../providers/lucia.provider';
|
||||
import { UsersRepository } from '../repositories/users.repository';
|
||||
import type { SignInEmailDto } from '../../../dtos/signin-email.dto';
|
||||
import type { RegisterEmailDto } from '../../../dtos/register-email.dto';
|
||||
import { LoginRequestsRepository } from '../repositories/login-requests.repository';
|
||||
|
||||
export class LoginRequestsService {
|
||||
async create(data: RegisterEmailDto) {
|
||||
// generate a token, expiry date, and hash
|
||||
const { token, expiry, hashedToken } = await this.tokensService.generateTokenWithExpiryAndHash(15, 'm');
|
||||
// save the login request to the database - ensuring we save the hashedToken
|
||||
await this.loginRequestsRepository.create({ email: data.email, hashedToken, expiresAt: expiry });
|
||||
// send the login request email
|
||||
await this.mailerService.sendLoginRequest({
|
||||
to: data.email,
|
||||
props: { token: token }
|
||||
});
|
||||
}
|
||||
|
||||
async verify(data: SignInEmailDto) {
|
||||
const validLoginRequest = await this.fetchValidRequest(data.email, data.token);
|
||||
if (!validLoginRequest) throw BadRequest('Invalid token');
|
||||
|
||||
let existingUser = await this.usersRepository.findOneByEmail(data.email);
|
||||
|
||||
if (!existingUser) {
|
||||
const newUser = await this.handleNewUserRegistration(data.email);
|
||||
return this.lucia.createSession(newUser.id, {});
|
||||
}
|
||||
|
||||
return this.lucia.createSession(existingUser.id, {});
|
||||
}
|
||||
|
||||
// Create a new user and send a welcome email - or other onboarding process
|
||||
private async handleNewUserRegistration(email: string) {
|
||||
const newUser = await this.usersRepository.create({ email, verified: true, avatar: null })
|
||||
this.mailerService.sendWelcome({ to: email, props: null });
|
||||
// TODO: add whatever onboarding process or extra data you need here
|
||||
return newUser
|
||||
}
|
||||
|
||||
// Fetch a valid request from the database, verify the token and burn the request if it is valid
|
||||
private async fetchValidRequest(email: string, token: string) {
|
||||
return await this.db.transaction(async (trx) => {
|
||||
// fetch the login request
|
||||
const loginRequest = await this.loginRequestsRepository.trxHost(trx).findOneByEmail(email)
|
||||
if (!loginRequest) return null;
|
||||
|
||||
// check if the token is valid
|
||||
const isValidRequest = await this.tokensService.verifyHashedToken(loginRequest.hashedToken, token);
|
||||
if (!isValidRequest) return null
|
||||
|
||||
// if the token is valid, burn the request
|
||||
await this.loginRequestsRepository.trxHost(trx).deleteById(loginRequest.id);
|
||||
return loginRequest
|
||||
})
|
||||
}
|
||||
}
|
||||
104
src/lib/server/api/services/mailer.service.ts
Normal file
104
src/lib/server/api/services/mailer.service.ts
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import nodemailer from 'nodemailer';
|
||||
import handlebars from 'handlebars';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { injectable } from 'tsyringe';
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* Service */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* ---------------------------------- About --------------------------------- */
|
||||
/*
|
||||
Services are responsible for handling business logic and data manipulation.
|
||||
They genreally call on repositories or other services to complete a use-case.
|
||||
*/
|
||||
/* ---------------------------------- Notes --------------------------------- */
|
||||
/*
|
||||
Services should be kept as clean and simple as possible.
|
||||
|
||||
Create private functions to handle complex logic and keep the public methods as
|
||||
simple as possible. This makes the service easier to read, test and understand.
|
||||
*/
|
||||
/* -------------------------------------------------------------------------- */
|
||||
|
||||
type SendMail = {
|
||||
to: string | string[];
|
||||
subject: string;
|
||||
html: string;
|
||||
};
|
||||
|
||||
type SendTemplate<T> = {
|
||||
to: string | string[];
|
||||
props: T;
|
||||
};
|
||||
|
||||
@injectable()
|
||||
export class MailerService {
|
||||
private nodemailer = nodemailer.createTransport({
|
||||
host: 'smtp.ethereal.email',
|
||||
port: 587,
|
||||
secure: false, // Use `true` for port 465, `false` for all other ports
|
||||
auth: {
|
||||
user: 'adella.hoppe@ethereal.email',
|
||||
pass: 'dshNQZYhATsdJ3ENke'
|
||||
}
|
||||
});
|
||||
|
||||
sendEmailVerificationToken(data: SendTemplate<{ token: string }>) {
|
||||
const template = handlebars.compile(this.getTemplate('email-verification-token'));
|
||||
return this.send({
|
||||
to: data.to,
|
||||
subject: 'Email Verification',
|
||||
html: template({ token: data.props.token })
|
||||
});
|
||||
}
|
||||
|
||||
sendEmailChangeNotification(data: SendTemplate<null>) {
|
||||
const template = handlebars.compile(this.getTemplate('email-change-notice'));
|
||||
return this.send({
|
||||
to: data.to,
|
||||
subject: 'Email Change Notice',
|
||||
html: template(null)
|
||||
});
|
||||
}
|
||||
|
||||
sendLoginRequest(data: SendTemplate<{ token: string }>) {
|
||||
const template = handlebars.compile(this.getTemplate('email-verification-token'));
|
||||
return this.send({
|
||||
to: data.to,
|
||||
subject: 'Login Request',
|
||||
html: template({ token: data.props.token })
|
||||
});
|
||||
}
|
||||
|
||||
sendWelcome(data: SendTemplate<null>) {
|
||||
const template = handlebars.compile(this.getTemplate('welcome'));
|
||||
return this.send({
|
||||
to: data.to,
|
||||
subject: 'Welcome!',
|
||||
html: template(null)
|
||||
});
|
||||
}
|
||||
|
||||
private async send({ to, subject, html }: SendMail) {
|
||||
const message = await this.nodemailer.sendMail({
|
||||
from: '"Example" <example@ethereal.email>', // sender address
|
||||
bcc: to,
|
||||
subject, // Subject line
|
||||
text: html,
|
||||
html
|
||||
});
|
||||
console.log(nodemailer.getTestMessageUrl(message));
|
||||
}
|
||||
|
||||
private getTemplate(template: string) {
|
||||
const __filename = fileURLToPath(import.meta.url); // get the resolved path to the file
|
||||
const __dirname = path.dirname(__filename); // get the name of the directory
|
||||
return fs.readFileSync(
|
||||
path.join(__dirname, `../infrastructure/email-templates/${template}.hbs`),
|
||||
'utf-8'
|
||||
);
|
||||
}
|
||||
}
|
||||
19
src/lib/server/api/services/queues.service.ts
Normal file
19
src/lib/server/api/services/queues.service.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { injectable } from "tsyringe";
|
||||
import RedisClient from 'ioredis'
|
||||
import { config } from "../common/config";
|
||||
import { Queue, Worker, type Processor } from 'bullmq';
|
||||
|
||||
@injectable()
|
||||
export class QueuesServices {
|
||||
connection = new RedisClient(config.REDIS_URL);
|
||||
|
||||
constructor() { }
|
||||
|
||||
createQueue(name: string) {
|
||||
return new Queue(name, { connection: this.connection })
|
||||
}
|
||||
|
||||
createWorker(name: string, prcoessor: Processor) {
|
||||
return new Worker(name, prcoessor, { connection: this.connection })
|
||||
}
|
||||
}
|
||||
33
src/lib/server/api/services/tokens.service.ts
Normal file
33
src/lib/server/api/services/tokens.service.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import { generateRandomString } from "oslo/crypto";
|
||||
import { TimeSpan, createDate, type TimeSpanUnit } from 'oslo';
|
||||
import { HashingService } from "./hashing.service";
|
||||
|
||||
export class TokensService {
|
||||
private readonly hashingService = new HashingService();
|
||||
|
||||
generateToken() {
|
||||
const alphabet = '23456789ACDEFGHJKLMNPQRSTUVWXYZ'; // alphabet with removed look-alike characters (0, 1, O, I)
|
||||
return generateRandomString(6, alphabet);
|
||||
}
|
||||
|
||||
generateTokenWithExpiry(number: number, lifespan: TimeSpanUnit) {
|
||||
return {
|
||||
token: this.generateToken(),
|
||||
expiry: createDate(new TimeSpan(number, lifespan))
|
||||
}
|
||||
}
|
||||
|
||||
async generateTokenWithExpiryAndHash(number: number, lifespan: TimeSpanUnit) {
|
||||
const token = this.generateToken()
|
||||
const hashedToken = await this.hashingService.hash(token)
|
||||
return {
|
||||
token,
|
||||
hashedToken,
|
||||
expiry: createDate(new TimeSpan(number, lifespan))
|
||||
}
|
||||
}
|
||||
|
||||
async verifyHashedToken(hashedToken: string, token: string) {
|
||||
return this.hashingService.verify(hashedToken, token)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue