From fdc359b5a865bd0a3e199e71d08d43428f180696 Mon Sep 17 00:00:00 2001 From: yamil Date: Fri, 8 Mar 2024 10:18:25 -0500 Subject: [PATCH 1/2] [Feat] Cookie session for web Eliminates the need for redis storage and the socket service, and enables a cookie at login for user authorization --- package.json | 1 + src/api/UserRouter.ts | 3 +++ src/api/middlewares/auth.ts | 6 +++++- src/controllers/UserController.ts | 29 +++++++++++++++++++++++++++++ src/interfaces/IUserController.ts | 1 + src/server.ts | 6 ------ yarn.lock | 5 +++++ 7 files changed, 44 insertions(+), 7 deletions(-) diff --git a/package.json b/package.json index dc84c65..cc86f1e 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "aws-sdk": "^2.1399.0", "axios": "^0.26.1", "compression": "^1.7.4", + "cookie": "^0.6.0", "cors": "^2.8.5", "crypto": "^1.0.1", "dotenv": "^16.0.0", diff --git a/src/api/UserRouter.ts b/src/api/UserRouter.ts index 0851057..eca9c1a 100644 --- a/src/api/UserRouter.ts +++ b/src/api/UserRouter.ts @@ -19,6 +19,9 @@ export class UserRouter implements IUserRouter { router.post('/login', (req, res, next) => this._controller.InitLogin(req, res, next).catch(next), ); + router.get('/logout', (req, res, next) => + this._controller.Logout(req, res, next).catch(next), + ); router.post('/revenuecat', (req, res, next) => this._subscription.RevenuecatWebhook(req, res, next).catch(next), ); diff --git a/src/api/middlewares/auth.ts b/src/api/middlewares/auth.ts index 3f7ca98..7fa053c 100644 --- a/src/api/middlewares/auth.ts +++ b/src/api/middlewares/auth.ts @@ -1,8 +1,12 @@ import { IRequest, IResponse, INext } from '../../interfaces/IRequest'; import JWT from 'jsonwebtoken'; +import cookie from 'cookie'; const loggedUser = (req: IResponse, _res: IRequest, next: INext) => { - const authorization = req.headers?.authorization; + const cookies = cookie.parse(req.headers.cookie || ''); + const cookieToken = cookies ? cookies[process.env.SESSION_COOKIE_NAME] : null; + + const authorization = req.headers?.authorization || cookieToken; if (authorization) { const token = authorization.replace('Bearer', '').trim(); try { diff --git a/src/controllers/UserController.ts b/src/controllers/UserController.ts index eff50c1..cf4a7d4 100644 --- a/src/controllers/UserController.ts +++ b/src/controllers/UserController.ts @@ -3,6 +3,7 @@ import { TYPES } from '../ContainerTypes'; import { IUserService } from '../interfaces/IUserService'; import { IRequest, IResponse } from '../interfaces/IRequest'; import { IUserController } from '../interfaces/IUserController'; +import cookie from 'cookie'; @injectable() export class UserController implements IUserController { @@ -29,6 +30,12 @@ export class UserController implements IUserController { client_id = await this._userService.getClientID({ origin: origin.replace('https://', '').replace('http://', ''), }); + if (!client_id) { + res + .status(422) + .json({ message: 'Your domain is not registered to use our API' }); + return; + } } const appleAuth = await this._userService.verifyToken({ token_id, @@ -69,9 +76,31 @@ export class UserController implements IUserController { session: appleAuth.sub, }); + if (!!client_id) { + // is from web enable 2 weeks + const isProd = process.env.NODE_ENV === 'production'; + res.setHeader( + 'Set-Cookie', + cookie.serialize(process.env.SESSION_COOKIE_NAME, token, { + httpOnly: true, + maxAge: 60 * 60 * 24 * 7 * 2, + sameSite: isProd ? 'none' : null, + secure: isProd, + path: '/', + }), + ); + return res.json({ email: user.email }); + } return res.json({ email: user.email, token }); } + public async Logout(req: IRequest, res: IResponse): Promise { + await res.clearCookie(process.env.SESSION_COOKIE_NAME, { path: '*' }); + return res.send({ + logout: true, + }); + } + public async DeleteAccount( req: IRequest, res: IResponse, diff --git a/src/interfaces/IUserController.ts b/src/interfaces/IUserController.ts index 0d768d1..1f63fd5 100644 --- a/src/interfaces/IUserController.ts +++ b/src/interfaces/IUserController.ts @@ -2,6 +2,7 @@ import { IRequest, IResponse, INext } from './IRequest'; export interface IUserController { InitLogin(req: IRequest, res: IResponse, _: INext): Promise; + Logout(req: IRequest, res: IResponse, _: INext): Promise; getAuth(req: IRequest, res: IResponse, _: INext): Promise; DeleteAccount(req: IRequest, res: IResponse, _: INext): Promise; } diff --git a/src/server.ts b/src/server.ts index d30aadc..03635e1 100644 --- a/src/server.ts +++ b/src/server.ts @@ -10,8 +10,6 @@ import { IRouterHttp } from './interfaces/IRouters'; import { TYPES } from './ContainerTypes'; import { handleError } from './api/middlewares/error'; import { IRestClientService } from './interfaces/IRestClientService'; -import { ISocketService } from './interfaces/ISocketService'; -import { ICacheService } from './interfaces/ICacheService'; import { ILoggerService } from './interfaces/ILoggerService'; import { IVersionMiddleware } from './interfaces/IVersionMiddleware'; import { IResponse, IRequest, INext } from './interfaces/IRequest'; @@ -20,8 +18,6 @@ import { IResponse, IRequest, INext } from './interfaces/IRequest'; export class Server { @inject(TYPES.RouterHttp) private _authRouter: IRouterHttp; @inject(TYPES.RestClientService) private _restClient: IRestClientService; - @inject(TYPES.SocketService) private _socketService: ISocketService; - @inject(TYPES.CacheService) private _cacheService: ICacheService; @inject(TYPES.LoggerService) private _logger: ILoggerService; @inject(TYPES.VersionMiddleware) private version: IVersionMiddleware; run(): void { @@ -48,8 +44,6 @@ export class Server { const httpServer = createServer(app); httpServer.listen(process.env.API_PORT || 5000, () => { this._logger.log({ origin: 'init app' }); - this._cacheService.connectCacheService(); - this._socketService.setupClient(httpServer); }); } } diff --git a/yarn.lock b/yarn.lock index 5fe5c5b..ab8c0db 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1617,6 +1617,11 @@ cookie@0.5.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== +cookie@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" + integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== + cookie@~0.4.1: version "0.4.2" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" From 550e762d44fe0e684154e20fc149709f76a00dec Mon Sep 17 00:00:00 2001 From: yamil Date: Sun, 10 Mar 2024 22:14:32 -0500 Subject: [PATCH 2/2] [Fix] cookie fixes --- src/api/middlewares/auth.ts | 18 +++++++++--------- src/controllers/UserController.ts | 5 +++-- src/utils/constant.ts | 3 +++ 3 files changed, 15 insertions(+), 11 deletions(-) create mode 100644 src/utils/constant.ts diff --git a/src/api/middlewares/auth.ts b/src/api/middlewares/auth.ts index 7fa053c..99535de 100644 --- a/src/api/middlewares/auth.ts +++ b/src/api/middlewares/auth.ts @@ -1,20 +1,20 @@ import { IRequest, IResponse, INext } from '../../interfaces/IRequest'; import JWT from 'jsonwebtoken'; import cookie from 'cookie'; +import { APP_CONST } from '../../utils/constant'; const loggedUser = (req: IResponse, _res: IRequest, next: INext) => { - const cookies = cookie.parse(req.headers.cookie || ''); - const cookieToken = cookies ? cookies[process.env.SESSION_COOKIE_NAME] : null; - - const authorization = req.headers?.authorization || cookieToken; - if (authorization) { - const token = authorization.replace('Bearer', '').trim(); - try { + try { + const cookies = cookie.parse(req.headers.cookie || ''); + const authorization = + req.headers.authorization || cookies[APP_CONST.SESSION_COOKIE_NAME]; + if (authorization) { + const token = authorization.replace('Bearer', '').trim(); const decoded = JWT.verify(token, process.env.APP_SECRET); req.user = decoded; - } catch (err) { - req.user = undefined; } + } catch (err) { + req.user = undefined; } return next(); }; diff --git a/src/controllers/UserController.ts b/src/controllers/UserController.ts index cf4a7d4..3ef58ba 100644 --- a/src/controllers/UserController.ts +++ b/src/controllers/UserController.ts @@ -4,6 +4,7 @@ import { IUserService } from '../interfaces/IUserService'; import { IRequest, IResponse } from '../interfaces/IRequest'; import { IUserController } from '../interfaces/IUserController'; import cookie from 'cookie'; +import { APP_CONST } from '../utils/constant'; @injectable() export class UserController implements IUserController { @@ -81,7 +82,7 @@ export class UserController implements IUserController { const isProd = process.env.NODE_ENV === 'production'; res.setHeader( 'Set-Cookie', - cookie.serialize(process.env.SESSION_COOKIE_NAME, token, { + cookie.serialize(APP_CONST.SESSION_COOKIE_NAME, token, { httpOnly: true, maxAge: 60 * 60 * 24 * 7 * 2, sameSite: isProd ? 'none' : null, @@ -95,7 +96,7 @@ export class UserController implements IUserController { } public async Logout(req: IRequest, res: IResponse): Promise { - await res.clearCookie(process.env.SESSION_COOKIE_NAME, { path: '*' }); + await res.clearCookie(APP_CONST.SESSION_COOKIE_NAME, { path: '/' }); return res.send({ logout: true, }); diff --git a/src/utils/constant.ts b/src/utils/constant.ts new file mode 100644 index 0000000..0fdd970 --- /dev/null +++ b/src/utils/constant.ts @@ -0,0 +1,3 @@ +export const APP_CONST = { + SESSION_COOKIE_NAME: 'bpCookie', +};