gateway 코드 이식 중
This commit is contained in:
@@ -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.");
|
||||||
|
}
|
||||||
|
);
|
||||||
@@ -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,
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
@@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -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>;
|
||||||
@@ -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>;
|
||||||
@@ -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,
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
@@ -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);
|
||||||
@@ -12,7 +12,8 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sammo/api_def": "workspace:^",
|
"@sammo/api_def": "workspace:^",
|
||||||
"@sammo/util": "workspace:^"
|
"@sammo/util": "workspace:^",
|
||||||
|
"@strpc/express": "workspace:^"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"lodash-es": "^4.17.21"
|
"lodash-es": "^4.17.21"
|
||||||
|
|||||||
@@ -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,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1 +1,2 @@
|
|||||||
export { UniqueNumberAllocator, type StateIncrementer } from './UniqueNumberAllocator.js';
|
export { UniqueNumberAllocator, type StateIncrementer } from './UniqueNumberAllocator.js';
|
||||||
|
export * from './StartSession.js';
|
||||||
@@ -23,6 +23,9 @@
|
|||||||
"composite": true
|
"composite": true
|
||||||
},
|
},
|
||||||
"references": [
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "../../@strpc/express"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "../util"
|
"path": "../util"
|
||||||
},
|
},
|
||||||
|
|||||||
Generated
+3
@@ -262,6 +262,9 @@ importers:
|
|||||||
'@sammo/util':
|
'@sammo/util':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../util
|
version: link:../util
|
||||||
|
'@strpc/express':
|
||||||
|
specifier: workspace:^
|
||||||
|
version: link:../../@strpc/express
|
||||||
devDependencies:
|
devDependencies:
|
||||||
lodash-es:
|
lodash-es:
|
||||||
specifier: ^4.17.21
|
specifier: ^4.17.21
|
||||||
|
|||||||
Reference in New Issue
Block a user