From 9b08ee4fed80870d1f471ef9ec79d63c94bb6e43 Mon Sep 17 00:00:00 2001 From: Andrew Besedin Date: Thu, 19 Feb 2026 01:22:32 +0300 Subject: [PATCH] add: add auth session --- shared/constants.ts | 2 + src/controllers/auth.controller.ts | 35 ++++++++++++ src/interfaces/bodies/auth/RequestAuthBody.ts | 13 +++++ .../responses/auth/RequestAuthRes.ts | 7 +++ src/models/AuthMessages.ts | 53 +++++++++++++++++++ src/routes/auth.router.ts | 8 +++ src/schemes/AuthMessage.ts | 46 ++++++++++++++++ 7 files changed, 164 insertions(+) create mode 100644 src/interfaces/bodies/auth/RequestAuthBody.ts create mode 100644 src/interfaces/responses/auth/RequestAuthRes.ts create mode 100644 src/models/AuthMessages.ts create mode 100644 src/schemes/AuthMessage.ts diff --git a/shared/constants.ts b/shared/constants.ts index 204f551..ae8726f 100644 --- a/shared/constants.ts +++ b/shared/constants.ts @@ -1 +1,3 @@ export const NON_NEGATIVE_REAL_NUMBER_REGEX = /^\d+(\.\d+)?$/; + +export const AUTH_MESSAGE_EXPIRATION_TIME_MS = 5 * 60 * 1000; // 5 minutes diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index 20dacc9..80e17e8 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -1,13 +1,38 @@ import jwt from 'jsonwebtoken'; import dotenv from 'dotenv'; import { Request, Response } from 'express'; +import crypto from 'crypto'; + import AuthData from '@/interfaces/bodies/user/AuthData.js'; +import RequestAuthBody from '@/interfaces/bodies/auth/RequestAuthBody.js'; +import authMessagesModel from '@/models/AuthMessages.js'; +import { AUTH_MESSAGE_EXPIRATION_TIME_MS } from 'shared/constants.js'; +import RequestAuthRes from '@/interfaces/responses/auth/RequestAuthRes.js'; import validateWallet from '../methods/validateWallet.js'; import userModel from '../models/User.js'; dotenv.config(); class AuthController { + requestAuth = async (req: Request, res: Response) => { + const { address, alias } = req.body as RequestAuthBody; + + const message = crypto.randomUUID(); + const expiresAt = new Date(Date.now() + AUTH_MESSAGE_EXPIRATION_TIME_MS); + + const authMessageRow = await authMessagesModel.create({ + address, + alias, + message, + expiresAt, + }); + + return res.status(200).send({ + success: true, + data: authMessageRow.message, + }); + }; + async auth(req: Request, res: Response) { try { const userData: AuthData = req.body.data; @@ -18,6 +43,16 @@ class AuthController { return res.status(400).send({ success: false, data: 'Invalid auth data' }); } + const authMessageRow = await authMessagesModel.findOne({ + address, + alias, + message, + }); + + if (!authMessageRow) { + return res.status(400).send({ success: false, data: 'Invalid auth message' }); + } + const dataValid = !!( userData && userData.address && diff --git a/src/interfaces/bodies/auth/RequestAuthBody.ts b/src/interfaces/bodies/auth/RequestAuthBody.ts new file mode 100644 index 0000000..6f1d368 --- /dev/null +++ b/src/interfaces/bodies/auth/RequestAuthBody.ts @@ -0,0 +1,13 @@ +import { body } from 'express-validator'; + +interface RequestAuthBody { + address: string; + alias: string; +} + +export const requestAuthBodyValidator = [ + body('address').isString().notEmpty(), + body('alias').isString().notEmpty(), +]; + +export default RequestAuthBody; diff --git a/src/interfaces/responses/auth/RequestAuthRes.ts b/src/interfaces/responses/auth/RequestAuthRes.ts new file mode 100644 index 0000000..a200f5f --- /dev/null +++ b/src/interfaces/responses/auth/RequestAuthRes.ts @@ -0,0 +1,7 @@ +interface RequestAuthRes { + success: true; + // Auth message + data: string; +} + +export default RequestAuthRes; diff --git a/src/models/AuthMessages.ts b/src/models/AuthMessages.ts new file mode 100644 index 0000000..7326da9 --- /dev/null +++ b/src/models/AuthMessages.ts @@ -0,0 +1,53 @@ +import { Transaction } from 'sequelize'; + +import AuthMessage from '@/schemes/AuthMessage'; + +class AuthMessagesModel { + create = async ( + { + address, + alias, + message, + expiresAt, + }: { + address: string; + alias: string; + message: string; + expiresAt: Date; + }, + { transaction }: { transaction?: Transaction } = {}, + ): Promise => { + const authMessage = await AuthMessage.create( + { + address, + alias, + message, + expiresAt, + }, + { transaction }, + ); + + return authMessage; + }; + + findOne = async ({ + address, + alias, + message, + }: { + address: string; + alias: string; + message: string; + }): Promise => + AuthMessage.findOne({ + where: { + address, + alias, + message, + }, + }); +} + +const authMessagesModel = new AuthMessagesModel(); + +export default authMessagesModel; diff --git a/src/routes/auth.router.ts b/src/routes/auth.router.ts index 77dfc5b..43f6af4 100644 --- a/src/routes/auth.router.ts +++ b/src/routes/auth.router.ts @@ -1,8 +1,16 @@ import express from 'express'; + +import middleware from '@/middleware/middleware.js'; +import { requestAuthBodyValidator } from '@/interfaces/bodies/auth/RequestAuthBody.js'; import authController from '../controllers/auth.controller.js'; const authRouter = express.Router(); +authRouter.post( + '/request-auth', + middleware.expressValidator(requestAuthBodyValidator), + authController.requestAuth.bind(authController), +); authRouter.post('/auth', authController.auth); export default authRouter; diff --git a/src/schemes/AuthMessage.ts b/src/schemes/AuthMessage.ts new file mode 100644 index 0000000..01e9725 --- /dev/null +++ b/src/schemes/AuthMessage.ts @@ -0,0 +1,46 @@ +import sequelize from '@/sequelize'; +import { DataTypes, Model } from 'sequelize'; + +class AuthMessage extends Model { + declare readonly id: number; + declare address: string; + declare alias: string; + declare message: string; + declare expiresAt: Date; + + declare readonly createdAt: Date; + declare readonly updatedAt: Date; +} + +AuthMessage.init( + { + id: { + type: DataTypes.INTEGER, + primaryKey: true, + autoIncrement: true, + }, + address: { + type: DataTypes.STRING, + allowNull: false, + }, + alias: { + type: DataTypes.STRING, + allowNull: false, + }, + message: { + type: DataTypes.STRING, + allowNull: false, + }, + expiresAt: { + type: DataTypes.DATE, + allowNull: false, + }, + }, + { + sequelize, + modelName: 'AuthMessage', + tableName: 'auth_messages', + }, +); + +export default AuthMessage;