From 93686c4912fd251784847107beefd7db855f5686 Mon Sep 17 00:00:00 2001 From: Hide_D Date: Fri, 22 Sep 2023 21:34:01 +0900 Subject: [PATCH] =?UTF-8?q?gateway=20=EC=BD=94=EB=93=9C=20=EC=9D=B4?= =?UTF-8?q?=EC=8B=9D=20=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- @sammo/gateway/src/api/GetGameLoginToken.ts | 22 +++++ @sammo/gateway/src/api/Install/index.ts | 0 @sammo/gateway/src/api/Install/initAdmin.ts | 0 .../gateway/src/api/Login/ChangePassword.ts | 18 ++++ @sammo/gateway/src/api/Login/LoginByID.ts | 61 +++++++++++++ @sammo/gateway/src/api/Login/LoginByToken.ts | 48 ++++++++++ @sammo/gateway/src/api/Login/ReqNonce.ts | 30 +++++++ @sammo/gateway/src/api/Login/index.ts | 13 +++ @sammo/gateway/src/api/gatewayAPI.ts | 9 ++ @sammo/gateway/src/api/index.ts | 9 ++ .../src/procDecorator/ReqGatewayLogin.ts | 33 +++++++ @sammo/gateway/src/schema/User.ts | 90 +++++++++++++++++++ @sammo/gateway/src/schema/UserLog.ts | 30 +++++++ @sammo/server_util/package.json | 3 +- @sammo/server_util/src/StartSession.ts | 58 ++++++++++++ @sammo/server_util/src/index.ts | 3 +- @sammo/server_util/tsconfig.json | 3 + pnpm-lock.yaml | 3 + 18 files changed, 431 insertions(+), 2 deletions(-) create mode 100644 @sammo/gateway/src/api/GetGameLoginToken.ts create mode 100644 @sammo/gateway/src/api/Install/index.ts create mode 100644 @sammo/gateway/src/api/Install/initAdmin.ts create mode 100644 @sammo/gateway/src/api/Login/ChangePassword.ts create mode 100644 @sammo/gateway/src/api/Login/LoginByID.ts create mode 100644 @sammo/gateway/src/api/Login/LoginByToken.ts create mode 100644 @sammo/gateway/src/api/Login/ReqNonce.ts create mode 100644 @sammo/gateway/src/api/Login/index.ts create mode 100644 @sammo/gateway/src/api/gatewayAPI.ts create mode 100644 @sammo/gateway/src/procDecorator/ReqGatewayLogin.ts create mode 100644 @sammo/gateway/src/schema/User.ts create mode 100644 @sammo/gateway/src/schema/UserLog.ts create mode 100644 @sammo/server_util/src/StartSession.ts diff --git a/@sammo/gateway/src/api/GetGameLoginToken.ts b/@sammo/gateway/src/api/GetGameLoginToken.ts new file mode 100644 index 0000000..024e235 --- /dev/null +++ b/@sammo/gateway/src/api/GetGameLoginToken.ts @@ -0,0 +1,22 @@ +import type { structure } from "@sammo/api_def/gateway"; +import { StartSession } from "@sammo/server_util"; +import { GET, type APIReturnType } from "@strpc/express"; +import { declProcDecorators } from "@strpc/express/proc_decorator"; +import { ReqGatewayLogin } from "../procDecorator/ReqGatewayLogin.js"; + +type BaseAPI = typeof structure.GetGameLoginToken; +type RType = APIReturnType; + +export const GameLoginTokenSessionKey = "GameLoginToken"; + +export const GetGameLoginToken = GET()(declProcDecorators( + StartSession, + ReqGatewayLogin, +))( + async (query, ctx): RType => { + return { + result: false, + reason: 'NotYetImplemented', + }; + } +); \ No newline at end of file diff --git a/@sammo/gateway/src/api/Install/index.ts b/@sammo/gateway/src/api/Install/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/@sammo/gateway/src/api/Install/initAdmin.ts b/@sammo/gateway/src/api/Install/initAdmin.ts new file mode 100644 index 0000000..e69de29 diff --git a/@sammo/gateway/src/api/Login/ChangePassword.ts b/@sammo/gateway/src/api/Login/ChangePassword.ts new file mode 100644 index 0000000..894c5c0 --- /dev/null +++ b/@sammo/gateway/src/api/Login/ChangePassword.ts @@ -0,0 +1,18 @@ +import type { structure } from "@sammo/api_def/gateway"; +import { StartSession } from "@sammo/server_util"; +import { GET, type APIReturnType } from "@strpc/express"; +import { declProcDecorators } from "@strpc/express/proc_decorator"; +import { ReqGatewayLogin } from "../../procDecorator/ReqGatewayLogin.js"; + +type BaseAPI = typeof structure.Login.ReqNonce; +type RType = APIReturnType; +const argValidator = undefined; + +export const ReqNonce = GET(argValidator)(declProcDecorators( + StartSession, + ReqGatewayLogin, +))( + (query, ctx): RType => { + throw new Error("Method not implemented."); + } +); \ No newline at end of file diff --git a/@sammo/gateway/src/api/Login/LoginByID.ts b/@sammo/gateway/src/api/Login/LoginByID.ts new file mode 100644 index 0000000..a9c99bf --- /dev/null +++ b/@sammo/gateway/src/api/Login/LoginByID.ts @@ -0,0 +1,61 @@ +import type { structure } from "@sammo/api_def/gateway"; +import { StartSession } from "@sammo/server_util"; +import { POST, type APIReturnType } from "@strpc/express"; +import { declProcDecorators } from "@strpc/express/proc_decorator"; +import { z } from "zod"; +import { loginCtxSessionKey, type GatewayLoginCtx } from "../../procDecorator/ReqGatewayLogin.js"; +import { delay } from "@sammo/util"; + +type BaseAPI = typeof structure.Login.LoginByID; +type RType = APIReturnType; +const LoginByIDReq = z.object({ + id: z.string(), + password: z.string(), +}); + +export const LoginByID = POST(LoginByIDReq)(declProcDecorators( + StartSession, +))( + async (query, ctx, req, res): RType => { + const id = query.id; + const password = query.password; + + //TODO: DB에서 뭔가 가져와야 함 + await delay(1); + + if (Math.random() < 0.3) { + return { + result: false, + reason: "로그인 실패", + reqOTP: false, + } + } + + if (Math.random() < 0.5) { + return { + result: false, + reason: "OTP 인증 필요", + reqOTP: true, + } + } + + const userID = 1; + const userName = "test"; + const userLevel = 1; + const nextToken: [number, string] = [1, "1234567890"]; + const loginCtx: GatewayLoginCtx = { + userID, + userName, + userLevel, + allowServerAction: new Set(), + loginDate: new Date(), + } + + ctx.session.setItem(loginCtxSessionKey, loginCtx); + + + return { + result: true, + nextToken, + } + }); \ No newline at end of file diff --git a/@sammo/gateway/src/api/Login/LoginByToken.ts b/@sammo/gateway/src/api/Login/LoginByToken.ts new file mode 100644 index 0000000..88e998b --- /dev/null +++ b/@sammo/gateway/src/api/Login/LoginByToken.ts @@ -0,0 +1,48 @@ +import type { structure } from "@sammo/api_def/gateway"; +import { StartSession } from "@sammo/server_util"; +import { POST, type APIReturnType } from "@strpc/express"; +import { declProcDecorators } from "@strpc/express/proc_decorator"; +import { z } from "zod"; +import { loginCtxSessionKey, type GatewayLoginCtx } from "../../procDecorator/ReqGatewayLogin.js"; + +type BaseAPI = typeof structure.Login.LoginByToken; +type RType = APIReturnType; + +const LoginByTokenReq = z.object({ + token_id: z.number(), + hashedToken: z.string(), +}); + +export const LoginByToken = POST(LoginByTokenReq)(declProcDecorators( + StartSession, +)) + (async (query, ctx) => { + query.hashedToken; + ctx.session.clear(); + + await delay(1); + //무언가 로그인 + //TODO: DB는 어디서 들고옴? + + const userID = 1; + const userName = "test"; + const userLevel = 1; + const nextToken: [number, string] = [1, "1234567890"]; + const loginCtx: GatewayLoginCtx = { + userID, + userName, + userLevel, + allowServerAction: new Map(), + loginDate: new Date(), + } + + ctx.session.setItem(loginCtxSessionKey, loginCtx); + + + //throw new Error("Method not implemented."); + return { + result: true, + nextToken, + } + + }); \ No newline at end of file diff --git a/@sammo/gateway/src/api/Login/ReqNonce.ts b/@sammo/gateway/src/api/Login/ReqNonce.ts new file mode 100644 index 0000000..100e22d --- /dev/null +++ b/@sammo/gateway/src/api/Login/ReqNonce.ts @@ -0,0 +1,30 @@ +import type { structure } from "@sammo/api_def/gateway"; +import { StartSession } from "@sammo/server_util"; +import { GET, type APIReturnType } from "@strpc/express"; +import { declProcDecorators } from "@strpc/express/proc_decorator"; + +type BaseAPI = typeof structure.Login.ReqNonce; +type RType = APIReturnType; + +export const ReqNonceSessionKey = 'loginNonce'; + +export const ReqNonce = GET(undefined)(declProcDecorators( + StartSession, +))( + async (query, ctx): RType => { + const nonce = ctx.session.getItem(ReqNonceSessionKey); + if (nonce !== undefined) { + return { + loginNonce: nonce, + result: true, + } + } + + const newNonce = "1234567890"; + ctx.session.setItem(ReqNonceSessionKey, newNonce); + return { + loginNonce: newNonce, + result: true, + } + } +) \ No newline at end of file diff --git a/@sammo/gateway/src/api/Login/index.ts b/@sammo/gateway/src/api/Login/index.ts new file mode 100644 index 0000000..4fc5a10 --- /dev/null +++ b/@sammo/gateway/src/api/Login/index.ts @@ -0,0 +1,13 @@ +import type { structure } from "../../../apiStructure/sammoGatewayAPI.js"; +import type { APINamespaceType } from "../../defs.js"; +import { LoginByID } from "./LoginByID.js"; +import { LoginByToken } from "./LoginByToken.js"; +import { ReqNonce } from "./ReqNonce.js"; +import { test } from "./test.js"; + +export const Login = { + LoginByID, + LoginByToken, + ReqNonce, + test, +} satisfies APINamespaceType; \ No newline at end of file diff --git a/@sammo/gateway/src/api/gatewayAPI.ts b/@sammo/gateway/src/api/gatewayAPI.ts new file mode 100644 index 0000000..e75767a --- /dev/null +++ b/@sammo/gateway/src/api/gatewayAPI.ts @@ -0,0 +1,9 @@ +import type { structure } from "../apiStructure/sammoGatewayAPI.js"; +import type { APINamespaceType } from "./defs.js"; +import { GetGameLoginToken } from "./GatewayAPI/GetGameLoginToken.js"; +import { Login } from "./GatewayAPI/Login/index.js"; + +export const sammoGatewayAPI = { + Login, + GetGameLoginToken +} satisfies APINamespaceType; \ No newline at end of file diff --git a/@sammo/gateway/src/api/index.ts b/@sammo/gateway/src/api/index.ts index e69de29..24869d8 100644 --- a/@sammo/gateway/src/api/index.ts +++ b/@sammo/gateway/src/api/index.ts @@ -0,0 +1,9 @@ +import type { structure } from "@sammo/api_def/gateway"; +import { GetGameLoginToken } from "./GetGameLoginToken.js"; +import { Login } from "./Login/index.js"; +import type { APINamespaceType } from "@strpc/express"; + +export const sammoGatewayAPI = { + Login, + GetGameLoginToken +} satisfies APINamespaceType; \ No newline at end of file diff --git a/@sammo/gateway/src/procDecorator/ReqGatewayLogin.ts b/@sammo/gateway/src/procDecorator/ReqGatewayLogin.ts new file mode 100644 index 0000000..27cadef --- /dev/null +++ b/@sammo/gateway/src/procDecorator/ReqGatewayLogin.ts @@ -0,0 +1,33 @@ +import type { SessionCtx } from "@sammo/server_util"; +import type { GatewayActionType, ServerActionType } from "../schema/User.js"; +import type { ProcDecorator } from "@strpc/express/proc_decorator"; + +export type GatewayLoginCtx = { + userID: number; + userName: string; + userLevel: number; + allowServerAction: Map>; + allowGatewayAction: Set; + loginDate: Date; +} +export const loginCtxSessionKey = 'loginCtx'; + +export function ReqGatewayLogin(): ProcDecorator { + return (ctx) => { + const loginCtx = ctx.session.getItem(loginCtxSessionKey); + if(!loginCtx){ + return [{ + result: false, + type: 'Required Login', + info: 'ReqLogin' + }, ctx]; + } + + return [{ + result: true, + },{ + ...loginCtx, + ...ctx, + }]; + } +} \ No newline at end of file diff --git a/@sammo/gateway/src/schema/User.ts b/@sammo/gateway/src/schema/User.ts new file mode 100644 index 0000000..3a3aed1 --- /dev/null +++ b/@sammo/gateway/src/schema/User.ts @@ -0,0 +1,90 @@ +import { Schema, model } from 'mongoose'; + +export type UserIDType = number; + +const validOAuthTypeList = ['KAKAO', 'NONE'] as const; +export type validOAuthType = typeof validOAuthTypeList[number]; + +const validServerActionTypeList = [ + 'Update', 'UpdateByGitPath', 'ShowErrorLog', + 'CloseServer', 'OpenServer', 'StopAndResumeServer', 'OpenVote', +] as const; +export type ServerActionType = typeof validServerActionTypeList[number]; + +const validGatewayActionTypeList = [ + 'Update', 'UpdateByGitPath', 'ShowErrorLog', 'ResetUserPassword', 'ChangeGatewayState', 'DeleteUser', +] as const; + +export type GatewayActionType = typeof validGatewayActionTypeList[number]; + +interface IUser { + _id: UserIDType; + + oauthID?: bigint; + id: string; + email: string; + + oauthType: validOAuthType; + oauthInfo?: object; + tokenValidUntil?: Date; + + userSalt: string; + hashedPassword: string; + + allowThirdPartyUse: boolean; + + userName: string; + allowServerAction?: Map; + allowGatewayAction?: ServerActionType[]; + penalty?: Map; + + picture?: string; + useImgSvr?: boolean; + + regDate: Date; + deleteAfter?: Date; +} + +export const User = new Schema({ + _id: { type: Number, required: true }, + + oauthID: { type: BigInt, required: false }, + id: { type: String, required: true }, + email: { type: String, required: true }, + + oauthType: { type: String, required: true, enum: validOAuthTypeList }, + oauthInfo: { type: Object, required: false }, + tokenValidUntil: { type: Date, required: false }, + + userSalt: { type: String, required: true }, + hashedPassword: { type: String, required: true }, + + allowThirdPartyUse: { type: Boolean, required: true }, + + userName: { type: String, required: true }, + allowServerAction: { + type: Map, required: false, of: { + type: Array, + of: { type: String, enum: validServerActionTypeList } + } + }, + allowGatewayAction: { + type: Array, required: false, of: { + type: String, enum: validGatewayActionTypeList + } + }, + penalty: { type: Map, required: false, of: Date }, + + picture: { type: String, required: false }, + useImgSvr: { type: Boolean, required: false }, + + regDate: { type: Date, required: true }, + deleteAfter: { type: Date, required: false }, +}, { autoIndex: false, autoCreate: false }) + .index({ id: 1 }, { unique: true }) + .index({ email: 1 }, { unique: true }) + .index({ oauthID: 1 }, { unique: true, sparse: true }) + .index({ deleteAfter: 1 }) // 자동 삭제 아님! + ; + +export default model('User', User); \ No newline at end of file diff --git a/@sammo/gateway/src/schema/UserLog.ts b/@sammo/gateway/src/schema/UserLog.ts new file mode 100644 index 0000000..2742802 --- /dev/null +++ b/@sammo/gateway/src/schema/UserLog.ts @@ -0,0 +1,30 @@ +import { Schema, model, } from 'mongoose'; +import { type UserIDType } from './User.js'; + +export type LogType = 'register' + | 'login_pw' | 'login_token' | 'login_oauth' | 'logout' + | 'change_pw' | 'reset_pw'; +const LogTypeList: LogType[] = [ + 'register', + 'login_pw', 'login_token', 'login_oauth', 'logout', + 'change_pw', 'reset_pw', +]; + +interface IUserLog { + userID: UserIDType; + logDate: Date; + logType: LogType; + action: object; +} + +export const UserLog = new Schema({ + userID: { type: Number, required: true }, + logDate: { type: Date, required: true }, + logType: { type: String, required: true, enum: LogTypeList }, + action: { type: Object, required: true }, +}, { autoIndex: false, autoCreate: false, }) + .index({ userID: 1, logDate: 1 }) + .index({ logDate: 1 }, { expireAfterSeconds: 60 * 60 * 24 * 365 * 3 }) + ; + +export default model('UserLog', UserLog); \ No newline at end of file diff --git a/@sammo/server_util/package.json b/@sammo/server_util/package.json index 74cb0c0..5c69aa4 100644 --- a/@sammo/server_util/package.json +++ b/@sammo/server_util/package.json @@ -12,7 +12,8 @@ "license": "MIT", "dependencies": { "@sammo/api_def": "workspace:^", - "@sammo/util": "workspace:^" + "@sammo/util": "workspace:^", + "@strpc/express": "workspace:^" }, "devDependencies": { "lodash-es": "^4.17.21" diff --git a/@sammo/server_util/src/StartSession.ts b/@sammo/server_util/src/StartSession.ts new file mode 100644 index 0000000..817365f --- /dev/null +++ b/@sammo/server_util/src/StartSession.ts @@ -0,0 +1,58 @@ +import type { Empty, ProcDecoratorGenerator } from "@strpc/express/proc_decorator"; +import { type Session, type SessionData } from "express-session"; + +export type SessionCtx = { + session: { + clear: () => Promise; + removeItem: (key: string) => boolean; + getItem: (key: string) => T | undefined; + setItem: (key: string, value: unknown) => void; + raw: Session & Partial & Record; + } +} + +export function StartSession(): ProcDecoratorGenerator { + return (inCtx, req) => { + if (!req.session) { + throw 'Express-session required'; + } + const sessionObj = { + raw: req.session + } as SessionCtx['session']; + sessionObj.clear = () => { + return new Promise((resolve) => { + sessionObj.raw = req.session.regenerate(resolve) as SessionCtx['session']['raw']; + }) + } + sessionObj.removeItem = (key: string): boolean => { + if (key in sessionObj.raw) { + delete sessionObj.raw[key]; + return true; + } + return false; + } + sessionObj.getItem = (key: string): T | undefined => { + if (!(key in sessionObj.raw)) { + return undefined; + } + return sessionObj.raw[key] as T; + } + sessionObj.setItem = (key: string, value: T | undefined): void => { + if (value === undefined) { + sessionObj.removeItem(key); + return; + } + sessionObj.raw[key] = value; + } + + return [ + { + result: true, + }, + { + session: sessionObj, + ...inCtx, + } + ] + } +} \ No newline at end of file diff --git a/@sammo/server_util/src/index.ts b/@sammo/server_util/src/index.ts index aaf34aa..8c59361 100644 --- a/@sammo/server_util/src/index.ts +++ b/@sammo/server_util/src/index.ts @@ -1 +1,2 @@ -export { UniqueNumberAllocator, type StateIncrementer } from './UniqueNumberAllocator.js'; \ No newline at end of file +export { UniqueNumberAllocator, type StateIncrementer } from './UniqueNumberAllocator.js'; +export * from './StartSession.js'; \ No newline at end of file diff --git a/@sammo/server_util/tsconfig.json b/@sammo/server_util/tsconfig.json index b70d41f..14f4d10 100644 --- a/@sammo/server_util/tsconfig.json +++ b/@sammo/server_util/tsconfig.json @@ -23,6 +23,9 @@ "composite": true }, "references": [ + { + "path": "../../@strpc/express" + }, { "path": "../util" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b7f8e9d..7066df1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -262,6 +262,9 @@ importers: '@sammo/util': specifier: workspace:^ version: link:../util + '@strpc/express': + specifier: workspace:^ + version: link:../../@strpc/express devDependencies: lodash-es: specifier: ^4.17.21