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",
|
||||
"dependencies": {
|
||||
"@sammo/api_def": "workspace:^",
|
||||
"@sammo/util": "workspace:^"
|
||||
"@sammo/util": "workspace:^",
|
||||
"@strpc/express": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"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
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "../../@strpc/express"
|
||||
},
|
||||
{
|
||||
"path": "../util"
|
||||
},
|
||||
|
||||
Generated
+3
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user