gateway 코드 이식 중

This commit is contained in:
2023-09-22 21:34:01 +09:00
parent 438ee85ed2
commit 93686c4912
18 changed files with 431 additions and 2 deletions
@@ -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<BaseAPI>;
export const GameLoginTokenSessionKey = "GameLoginToken";
export const GetGameLoginToken = GET<BaseAPI>()(declProcDecorators(
StartSession,
ReqGatewayLogin,
))(
async (query, ctx): RType => {
return {
result: false,
reason: 'NotYetImplemented',
};
}
);
@@ -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<BaseAPI>;
const argValidator = undefined;
export const ReqNonce = GET<BaseAPI>(argValidator)(declProcDecorators(
StartSession,
ReqGatewayLogin,
))(
(query, ctx): RType => {
throw new Error("Method not implemented.");
}
);
+61
View File
@@ -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<BaseAPI>;
const LoginByIDReq = z.object({
id: z.string(),
password: z.string(),
});
export const LoginByID = POST<BaseAPI>(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,
}
});
@@ -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<BaseAPI>;
const LoginByTokenReq = z.object({
token_id: z.number(),
hashedToken: z.string(),
});
export const LoginByToken = POST<BaseAPI>(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,
}
});
+30
View File
@@ -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<BaseAPI>;
export const ReqNonceSessionKey = 'loginNonce';
export const ReqNonce = GET<BaseAPI>(undefined)(declProcDecorators(
StartSession,
))(
async (query, ctx): RType => {
const nonce = ctx.session.getItem<string>(ReqNonceSessionKey);
if (nonce !== undefined) {
return {
loginNonce: nonce,
result: true,
}
}
const newNonce = "1234567890";
ctx.session.setItem(ReqNonceSessionKey, newNonce);
return {
loginNonce: newNonce,
result: true,
}
}
)
+13
View File
@@ -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<typeof structure.Login>;
+9
View File
@@ -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<typeof structure>;
+9
View File
@@ -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<typeof structure>;
@@ -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<string, Set<ServerActionType>>;
allowGatewayAction: Set<GatewayActionType>;
loginDate: Date;
}
export const loginCtxSessionKey = 'loginCtx';
export function ReqGatewayLogin<Q extends SessionCtx>(): ProcDecorator<GatewayLoginCtx & Q, Q> {
return (ctx) => {
const loginCtx = ctx.session.getItem<GatewayLoginCtx>(loginCtxSessionKey);
if(!loginCtx){
return [{
result: false,
type: 'Required Login',
info: 'ReqLogin'
}, ctx];
}
return [{
result: true,
},{
...loginCtx,
...ctx,
}];
}
}
+90
View File
@@ -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<string, ServerActionType[]>;
allowGatewayAction?: ServerActionType[];
penalty?: Map<string, Date>;
picture?: string;
useImgSvr?: boolean;
regDate: Date;
deleteAfter?: Date;
}
export const User = new Schema<IUser>({
_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<IUser>('User', User);
+30
View File
@@ -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<IUserLog>({
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<IUserLog>('UserLog', UserLog);
+2 -1
View File
@@ -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"
+58
View File
@@ -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<void>;
removeItem: (key: string) => boolean;
getItem: <T>(key: string) => T | undefined;
setItem: (key: string, value: unknown) => void;
raw: Session & Partial<SessionData> & Record<string, unknown>;
}
}
export function StartSession<Q extends object = Empty>(): ProcDecoratorGenerator<SessionCtx, Q> {
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 = <T>(key: string): T | undefined => {
if (!(key in sessionObj.raw)) {
return undefined;
}
return sessionObj.raw[key] as T;
}
sessionObj.setItem = <T>(key: string, value: T | undefined): void => {
if (value === undefined) {
sessionObj.removeItem(key);
return;
}
sessionObj.raw[key] = value;
}
return [
{
result: true,
},
{
session: sessionObj,
...inCtx,
}
]
}
}
+2 -1
View File
@@ -1 +1,2 @@
export { UniqueNumberAllocator, type StateIncrementer } from './UniqueNumberAllocator.js';
export { UniqueNumberAllocator, type StateIncrementer } from './UniqueNumberAllocator.js';
export * from './StartSession.js';
+3
View File
@@ -23,6 +23,9 @@
"composite": true
},
"references": [
{
"path": "../../@strpc/express"
},
{
"path": "../util"
},
+3
View File
@@ -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