Compare commits
95
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2997d5dc21 | ||
|
|
bfbc0134de | ||
|
|
ffd3ab681e | ||
|
|
4e629e0b1e | ||
|
|
438df6cd3c | ||
|
|
9c9d9e9545 | ||
|
|
93686c4912 | ||
|
|
438ee85ed2 | ||
|
|
295d39327b | ||
|
|
b8d57a014a | ||
|
|
0f9d5c2dc9 | ||
|
|
3013554cef
|
||
|
|
32b41dd07a
|
||
|
|
f4640abe17
|
||
|
|
56a9c5bc45
|
||
|
|
65bf02a3aa
|
||
|
|
08890309c2
|
||
|
|
194b213f61
|
||
|
|
f8b8ea6cbe
|
||
|
|
0ee7066679
|
||
|
|
b850eb285b
|
||
|
|
6cbbc3222a
|
||
|
|
ae7701ee18
|
||
|
|
72c6f5ae64
|
||
|
|
ad4ac9dfb2
|
||
|
|
635d7f167d
|
||
|
|
87c239610a
|
||
|
|
199938f2b2
|
||
|
|
60848987eb
|
||
|
|
a37baa64fe
|
||
|
|
ae22fc7c28
|
||
|
|
17977b4d96
|
||
|
|
0fda257ca0
|
||
|
|
aec88333c2
|
||
|
|
df8b5e854b
|
||
|
|
c012a81377
|
||
|
|
b6cdd49449
|
||
|
|
837cff225b
|
||
|
|
de0b851217
|
||
|
|
bfe0b99d44
|
||
|
|
7716c6936b
|
||
|
|
8e96f6082d
|
||
|
|
302bab518e
|
||
|
|
0ba0b83433
|
||
|
|
3a06e44e85
|
||
|
|
81d16aaba2
|
||
|
|
0c511d27c5 | ||
|
|
1214036091
|
||
|
|
13d9662439
|
||
|
|
e381dd5a7e
|
||
|
|
c7d0702e66
|
||
|
|
bf35eb4c09
|
||
|
|
deb989e45b
|
||
|
|
96b18ad5ff
|
||
|
|
0d57ff9783
|
||
|
|
ff423189e9
|
||
|
|
507cba34eb
|
||
|
|
b30dde442b
|
||
|
|
3f7e645e40
|
||
|
|
d928241e00
|
||
|
|
35f5726659
|
||
|
|
ce1749e090
|
||
|
|
4b6d48850e
|
||
|
|
31fa264160
|
||
|
|
69de10642e
|
||
|
|
691f5e79ce
|
||
|
|
3256620276
|
||
|
|
a1502ebc16
|
||
|
|
1d751ea045
|
||
|
|
cca7214446
|
||
|
|
257644aff7
|
||
|
|
5e216e5f67
|
||
|
|
19a19d8a2f
|
||
|
|
3432009213
|
||
|
|
826b4212e5
|
||
|
|
7352cdbf23
|
||
|
|
2b27b8b427
|
||
|
|
5b6751f420
|
||
|
|
2fb22ed6a9
|
||
|
|
743ab3e1aa
|
||
|
|
92a67315f3
|
||
|
|
8130c731e0
|
||
|
|
5ba073c125
|
||
|
|
45d05f900f
|
||
|
|
580dd4aeee
|
||
|
|
b88fdf85e1
|
||
|
|
421d75f665
|
||
|
|
a261b45e5d
|
||
|
|
a39543e718
|
||
|
|
f2ad6ad897
|
||
|
|
bb28815f93
|
||
|
|
9557e9b099
|
||
|
|
0622c534bc
|
||
|
|
58562e2c99
|
||
|
|
1287486d74
|
@@ -0,0 +1,17 @@
|
||||
/* eslint-env node */
|
||||
require('@rushstack/eslint-patch/modern-module-resolution')
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
parser: "vue-eslint-parser",
|
||||
'extends': [
|
||||
'plugin:vue/vue3-essential',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'eslint:recommended',
|
||||
'@vue/eslint-config-typescript',
|
||||
'@vue/eslint-config-prettier/skip-formatting',
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist_server
|
||||
coverage
|
||||
*.local
|
||||
|
||||
/cypress/videos/
|
||||
/cypress/screenshots/
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
*.tsbuildinfo
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/prettierrc",
|
||||
"semi": false,
|
||||
"tabWidth": 2,
|
||||
"singleQuote": true,
|
||||
"printWidth": 100,
|
||||
"trailingComma": "none"
|
||||
}
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "@sammo/api_def",
|
||||
"version": "1.0.1",
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"exports": {
|
||||
".": "./dist/index.js",
|
||||
"./gateway": "./dist/gateway.js",
|
||||
"./def": "./dist/def/index.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc --build"
|
||||
},
|
||||
"author": "",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sammo/crypto": "workspace:^",
|
||||
"@sammo/secure_token": "workspace:^",
|
||||
"@sammo/util": "workspace:^",
|
||||
"@strpc/def": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"zod": "^3.22.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.22.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export const hello = "hello";
|
||||
@@ -0,0 +1,71 @@
|
||||
import { GET, POST, type DefAPINamespace } from "@strpc/def";
|
||||
|
||||
//굳이 할 필요는 없지만, d.ts가 깔끔해짐
|
||||
import type {
|
||||
InvalidResponse, ValidResponse
|
||||
} from "@strpc/def";
|
||||
import type {
|
||||
ArgDeleteAPI,
|
||||
ArgGetAPI,
|
||||
ArgHeadAPI,
|
||||
ArgPatchAPI,
|
||||
ArgPostAPI,
|
||||
ArgPutAPI,
|
||||
EmptyDeleteAPI,
|
||||
EmptyGetAPI,
|
||||
EmptyHeadAPI,
|
||||
EmptyPatchAPI,
|
||||
EmptyPostAPI,
|
||||
EmptyPutAPI,
|
||||
} from "@strpc/def/types";
|
||||
|
||||
|
||||
export type LoginResponse = {
|
||||
result: true,
|
||||
nextToken: [number, string] | undefined,
|
||||
}
|
||||
|
||||
export type LoginFailed = {
|
||||
result: false,
|
||||
reqOTP: boolean,
|
||||
reason: string,
|
||||
}
|
||||
|
||||
|
||||
export type AutoLoginNonceResponse = {
|
||||
result: true,
|
||||
loginNonce: string,
|
||||
};
|
||||
|
||||
export type AutoLoginResponse = {
|
||||
result: true,
|
||||
nextToken: [number, string] | undefined,
|
||||
}
|
||||
|
||||
|
||||
export type AutoLoginFailed = {
|
||||
result: false,
|
||||
silent: boolean,
|
||||
reason: string,
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export const structure = {
|
||||
Login: {
|
||||
LoginByID: POST<{
|
||||
id: string,
|
||||
password: string,
|
||||
}, LoginResponse, LoginFailed>(),
|
||||
LoginByToken: POST<{
|
||||
hashedToken: string,
|
||||
token_id: number,
|
||||
}, AutoLoginResponse, AutoLoginFailed>(),
|
||||
ReqNonce: GET<AutoLoginNonceResponse, AutoLoginFailed>(),
|
||||
},
|
||||
GetGameLoginToken: GET<{
|
||||
result: true,
|
||||
gameLoginToken: string,
|
||||
userID: number,
|
||||
}>(),
|
||||
|
||||
} satisfies DefAPINamespace;
|
||||
@@ -0,0 +1,33 @@
|
||||
import type {
|
||||
DefAPINamespace,
|
||||
} from "@strpc/def";
|
||||
|
||||
//굳이 할 필요는 없지만, d.ts가 깔끔해짐
|
||||
import type {
|
||||
InvalidResponse, ValidResponse
|
||||
} from "@strpc/def";
|
||||
import type {
|
||||
ArgDeleteAPI,
|
||||
ArgGetAPI,
|
||||
ArgHeadAPI,
|
||||
ArgPatchAPI,
|
||||
ArgPostAPI,
|
||||
ArgPutAPI,
|
||||
EmptyDeleteAPI,
|
||||
EmptyGetAPI,
|
||||
EmptyHeadAPI,
|
||||
EmptyPatchAPI,
|
||||
EmptyPostAPI,
|
||||
EmptyPutAPI,
|
||||
} from "@strpc/def/types";
|
||||
import { GET, POST } from '@strpc/def';
|
||||
|
||||
export type {
|
||||
InferResponse,
|
||||
InferError,
|
||||
InferQuery,
|
||||
} from "@strpc/def";
|
||||
export type * as def from './def/index.js';
|
||||
|
||||
export const structure = {
|
||||
} satisfies DefAPINamespace;
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
|
||||
"path": "../util"
|
||||
},
|
||||
{
|
||||
"path": "../secure_token"
|
||||
},
|
||||
{
|
||||
"path": "../../@strpc/def"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"name": "@sammo/client",
|
||||
"version": "1.0.1",
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check build-only",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false",
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||
"format": "prettier --write src/"
|
||||
},
|
||||
"author": "",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@sammo/game_logic": "workspace:^",
|
||||
"@sammo/util": "workspace:^",
|
||||
"@strpc/def": "workspace:^",
|
||||
"bootstrap": "^5.3.1",
|
||||
"bootstrap-vue-next": "^0.9.26",
|
||||
"bson": "^5.4.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"ky": "^1.0.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"pinia": "^2.1.4",
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "^4.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node20": "^20.1.2",
|
||||
"@types/lodash-es": "^4.17.9",
|
||||
"@types/node": "^20.6.3",
|
||||
"@vue/eslint-config-prettier": "^8.0.0",
|
||||
"@vue/eslint-config-typescript": "^11.0.3",
|
||||
"@vue/tsconfig": "^0.4.0",
|
||||
"eslint": "^8.50.0",
|
||||
"eslint-plugin-vue": "^9.15.1",
|
||||
"prettier": "^3.0.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"tslib": "^2.6.2",
|
||||
"vite": "^4.4.6",
|
||||
"vue-tsc": "^1.8.6",
|
||||
"zod": "^3.22.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.22.2"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,85 @@
|
||||
<script setup lang="ts">
|
||||
import { RouterLink, RouterView } from 'vue-router'
|
||||
import HelloWorld from './components/HelloWorld.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header>
|
||||
<img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />
|
||||
|
||||
<div class="wrapper">
|
||||
<HelloWorld msg="You did it!" />
|
||||
|
||||
<nav>
|
||||
<RouterLink to="/">Home</RouterLink>
|
||||
<RouterLink to="/about">About</RouterLink>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<RouterView />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
header {
|
||||
line-height: 1.5;
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: block;
|
||||
margin: 0 auto 2rem;
|
||||
}
|
||||
|
||||
nav {
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
nav a.router-link-exact-active {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
nav a.router-link-exact-active:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
nav a {
|
||||
display: inline-block;
|
||||
padding: 0 1rem;
|
||||
border-left: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
nav a:first-of-type {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
header {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
padding-right: calc(var(--section-gap) / 2);
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin: 0 2rem 0 0;
|
||||
}
|
||||
|
||||
header .wrapper {
|
||||
display: flex;
|
||||
place-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
nav {
|
||||
text-align: left;
|
||||
margin-left: -1rem;
|
||||
font-size: 1rem;
|
||||
|
||||
padding: 1rem 0;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,86 @@
|
||||
/* color palette from <https://github.com/vuejs/theme> */
|
||||
:root {
|
||||
--vt-c-white: #ffffff;
|
||||
--vt-c-white-soft: #f8f8f8;
|
||||
--vt-c-white-mute: #f2f2f2;
|
||||
|
||||
--vt-c-black: #181818;
|
||||
--vt-c-black-soft: #222222;
|
||||
--vt-c-black-mute: #282828;
|
||||
|
||||
--vt-c-indigo: #2c3e50;
|
||||
|
||||
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
||||
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
||||
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
||||
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
||||
|
||||
--vt-c-text-light-1: var(--vt-c-indigo);
|
||||
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
||||
--vt-c-text-dark-1: var(--vt-c-white);
|
||||
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
||||
}
|
||||
|
||||
/* semantic color variables for this project */
|
||||
:root {
|
||||
--color-background: var(--vt-c-white);
|
||||
--color-background-soft: var(--vt-c-white-soft);
|
||||
--color-background-mute: var(--vt-c-white-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-light-2);
|
||||
--color-border-hover: var(--vt-c-divider-light-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-light-1);
|
||||
--color-text: var(--vt-c-text-light-1);
|
||||
|
||||
--section-gap: 160px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-background: var(--vt-c-black);
|
||||
--color-background-soft: var(--vt-c-black-soft);
|
||||
--color-background-mute: var(--vt-c-black-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-dark-2);
|
||||
--color-border-hover: var(--vt-c-divider-dark-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-dark-1);
|
||||
--color-text: var(--vt-c-text-dark-2);
|
||||
}
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
color: var(--color-text);
|
||||
background: var(--color-background);
|
||||
transition:
|
||||
color 0.5s,
|
||||
background-color 0.5s;
|
||||
line-height: 1.6;
|
||||
font-family:
|
||||
Inter,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
Roboto,
|
||||
Oxygen,
|
||||
Ubuntu,
|
||||
Cantarell,
|
||||
'Fira Sans',
|
||||
'Droid Sans',
|
||||
'Helvetica Neue',
|
||||
sans-serif;
|
||||
font-size: 15px;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
|
||||
|
After Width: | Height: | Size: 276 B |
@@ -0,0 +1,35 @@
|
||||
@import './base.css';
|
||||
|
||||
#app {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
a,
|
||||
.green {
|
||||
text-decoration: none;
|
||||
color: hsla(160, 100%, 37%, 1);
|
||||
transition: 0.4s;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
a:hover {
|
||||
background-color: hsla(160, 100%, 37%, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
body {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
#app {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
msg: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="greetings">
|
||||
<h1 class="green">{{ msg }}</h1>
|
||||
<h3>
|
||||
You’ve successfully created a project with
|
||||
<a href="https://vitejs.dev/" target="_blank" rel="noopener">Vite</a> +
|
||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>. What's next?
|
||||
</h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
h1 {
|
||||
font-weight: 500;
|
||||
font-size: 2.6rem;
|
||||
position: relative;
|
||||
top: -10px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.greetings h1,
|
||||
.greetings h3 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.greetings h1,
|
||||
.greetings h3 {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,86 @@
|
||||
<script setup lang="ts">
|
||||
import WelcomeItem from './WelcomeItem.vue'
|
||||
import DocumentationIcon from './icons/IconDocumentation.vue'
|
||||
import ToolingIcon from './icons/IconTooling.vue'
|
||||
import EcosystemIcon from './icons/IconEcosystem.vue'
|
||||
import CommunityIcon from './icons/IconCommunity.vue'
|
||||
import SupportIcon from './icons/IconSupport.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<DocumentationIcon />
|
||||
</template>
|
||||
<template #heading>Documentation</template>
|
||||
|
||||
Vue’s
|
||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
|
||||
provides you with all information you need to get started.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<ToolingIcon />
|
||||
</template>
|
||||
<template #heading>Tooling</template>
|
||||
|
||||
This project is served and bundled with
|
||||
<a href="https://vitejs.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The
|
||||
recommended IDE setup is
|
||||
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a> +
|
||||
<a href="https://github.com/johnsoncodehk/volar" target="_blank" rel="noopener">Volar</a>. If
|
||||
you need to test your components and web pages, check out
|
||||
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a> and
|
||||
<a href="https://on.cypress.io/component" target="_blank">Cypress Component Testing</a>.
|
||||
|
||||
<br />
|
||||
|
||||
More instructions are available in <code>README.md</code>.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<EcosystemIcon />
|
||||
</template>
|
||||
<template #heading>Ecosystem</template>
|
||||
|
||||
Get official tools and libraries for your project:
|
||||
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
|
||||
<a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
|
||||
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and
|
||||
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If
|
||||
you need more resources, we suggest paying
|
||||
<a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
|
||||
a visit.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<CommunityIcon />
|
||||
</template>
|
||||
<template #heading>Community</template>
|
||||
|
||||
Got stuck? Ask your question on
|
||||
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>, our official
|
||||
Discord server, or
|
||||
<a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener"
|
||||
>StackOverflow</a
|
||||
>. You should also subscribe to
|
||||
<a href="https://news.vuejs.org" target="_blank" rel="noopener">our mailing list</a> and follow
|
||||
the official
|
||||
<a href="https://twitter.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
|
||||
twitter account for latest news in the Vue world.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<SupportIcon />
|
||||
</template>
|
||||
<template #heading>Support Vue</template>
|
||||
|
||||
As an independent project, Vue relies on community backing for its sustainability. You can help
|
||||
us by
|
||||
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
|
||||
</WelcomeItem>
|
||||
</template>
|
||||
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<div class="item">
|
||||
<i>
|
||||
<slot name="icon"></slot>
|
||||
</i>
|
||||
<div class="details">
|
||||
<h3>
|
||||
<slot name="heading"></slot>
|
||||
</h3>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.item {
|
||||
margin-top: 2rem;
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.details {
|
||||
flex: 1;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
i {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
place-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.4rem;
|
||||
color: var(--color-heading);
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.item {
|
||||
margin-top: 0;
|
||||
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
|
||||
}
|
||||
|
||||
i {
|
||||
top: calc(50% - 25px);
|
||||
left: -26px;
|
||||
position: absolute;
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-background);
|
||||
border-radius: 8px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.item:before {
|
||||
content: ' ';
|
||||
border-left: 1px solid var(--color-border);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: calc(50% + 25px);
|
||||
height: calc(50% - 25px);
|
||||
}
|
||||
|
||||
.item:after {
|
||||
content: ' ';
|
||||
border-left: 1px solid var(--color-border);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: calc(50% + 25px);
|
||||
height: calc(50% - 25px);
|
||||
}
|
||||
|
||||
.item:first-of-type:before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.item:last-of-type:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
||||
<path
|
||||
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
|
||||
<path
|
||||
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
||||
<path
|
||||
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
@@ -0,0 +1,19 @@
|
||||
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
class="iconify iconify--mdi"
|
||||
width="24"
|
||||
height="24"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
</template>
|
||||
@@ -0,0 +1,14 @@
|
||||
import './assets/main.css'
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
import App from './App.vue'
|
||||
import router from './router/index.js'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
|
||||
app.mount('#app')
|
||||
@@ -0,0 +1,23 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import HomeView from '../views/HomeView.vue'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: HomeView
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
name: 'about',
|
||||
// route level code-splitting
|
||||
// this generates a separate chunk (About.[hash].js) for this route
|
||||
// which is lazy-loaded when the route is visited.
|
||||
component: () => import('../views/AboutView.vue')
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -0,0 +1,12 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useCounterStore = defineStore('counter', () => {
|
||||
const count = ref(0)
|
||||
const doubleCount = computed(() => count.value * 2)
|
||||
function increment() {
|
||||
count.value++
|
||||
}
|
||||
|
||||
return { count, doubleCount, increment }
|
||||
})
|
||||
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div class="about">
|
||||
<h1>This is an about page</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@media (min-width: 1024px) {
|
||||
.about {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import TheWelcome from '../components/TheWelcome.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<TheWelcome />
|
||||
</main>
|
||||
</template>
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
},
|
||||
"exclude": [
|
||||
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
|
||||
"path": "../util"
|
||||
},
|
||||
{
|
||||
"path": "../secure_token"
|
||||
},
|
||||
{
|
||||
"path": "../../@strpc/def"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "@tsconfig/node20/tsconfig.json",
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"nightwatch.conf.*",
|
||||
"playwright.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"],
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
'@util': fileURLToPath(new URL('./server/util', import.meta.url)),
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,12 @@
|
||||
module.exports = {
|
||||
root: false,
|
||||
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['@typescript-eslint'],
|
||||
env: {
|
||||
node: true,
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-vars': 'off'
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/** @type {import('ts-jest').JestConfigWithTsJest} */
|
||||
const jestConfig = {
|
||||
preset: 'ts-jest/presets/default-esm',
|
||||
extensionsToTreatAsEsm: ['.ts'],
|
||||
testEnvironment: 'node',
|
||||
rootDir: '.',
|
||||
transform: {
|
||||
'^.+\\.tsx?$': [
|
||||
'ts-jest',
|
||||
{
|
||||
useESM: true,
|
||||
tsconfig: 'tsconfig.jest.json',
|
||||
},
|
||||
],
|
||||
},
|
||||
moduleFileExtensions: [
|
||||
'js',
|
||||
'mjs',
|
||||
'ts',
|
||||
'mts',
|
||||
'tsx',
|
||||
'jsx',
|
||||
],
|
||||
moduleNameMapper: {
|
||||
'^(\\.{1,2}/.*)\\.js$': '$1',
|
||||
"^lodash-es$": "lodash",
|
||||
"^lodash-es/(.*)$": "<rootDir>/node_modules/lodash/$1",
|
||||
},
|
||||
modulePathIgnorePatterns: [
|
||||
'<rootDir>/dist/',
|
||||
],
|
||||
moduleDirectories: [
|
||||
"node_modules",
|
||||
".*/@sammo/util"
|
||||
],
|
||||
};
|
||||
|
||||
export default jestConfig
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "@sammo/crypto",
|
||||
"version": "1.0.1",
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"exports": {
|
||||
".": "./dist/index.js",
|
||||
"./AES": "./dist/AES.js",
|
||||
"./ECDSA": "./dist/ECDSA.js",
|
||||
"./ECDHe": "./dist/ECDHe.js",
|
||||
"./ECDHe_AES": "./dist/ECDHe_AES.js",
|
||||
"./ECKey": "./dist/ECKey.js",
|
||||
"./PBKDF2": "./dist/PBKDF2.js",
|
||||
"./SHA2": "./dist/SHA2.js",
|
||||
"./RawTypes": "./dist/RawTypes.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc --build",
|
||||
"test": "jest"
|
||||
},
|
||||
"author": "",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sammo/util": "workspace:^"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.4",
|
||||
"@types/lodash": "^4.14.198",
|
||||
"@types/lodash-es": "^4.17.9",
|
||||
"@types/node": "^20.6.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.0",
|
||||
"@typescript-eslint/parser": "^6.7.0",
|
||||
"bson": "^5.4.0",
|
||||
"buffer": "^6.0.3",
|
||||
"eslint": "^8.49.0",
|
||||
"jest": "^29.7.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"ts-jest": "^29.1.1",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bson": "^5.4.0",
|
||||
"buffer": "^6.0.3",
|
||||
"lodash-es": "^4.17.21"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import { AES_GCM_Decrypt, AES_GCM_Encrypt } from "./AES.js";
|
||||
|
||||
type TestType = {
|
||||
key: Buffer,
|
||||
IV: Buffer,
|
||||
PT: Buffer,
|
||||
AAD?: Buffer,
|
||||
CT: Buffer,
|
||||
failed?: boolean,
|
||||
};
|
||||
|
||||
type RawTestType = {
|
||||
key: string,
|
||||
IV: string,
|
||||
PT: string,
|
||||
AAD?: string,
|
||||
CT: string,
|
||||
failed?: boolean,
|
||||
}
|
||||
|
||||
function convertItem(obj: RawTestType): TestType {
|
||||
return {
|
||||
key: Buffer.from(obj.key, 'hex'),
|
||||
IV: Buffer.from(obj.IV, 'hex'),
|
||||
PT: Buffer.from(obj.PT, 'hex'),
|
||||
AAD: obj.AAD ? Buffer.from(obj.AAD, 'hex') : undefined,
|
||||
CT: Buffer.from(obj.CT, 'hex'),
|
||||
failed: obj.failed,
|
||||
}
|
||||
}
|
||||
|
||||
const tests: TestType[] = [
|
||||
{
|
||||
key: '92e11dcdaa866f5ce790fd24501f92509aacf4cb8b1339d50c9c1240935dd08b',
|
||||
IV : 'ac93a1a6145299bde902f21a',
|
||||
PT : '2d71bcfa914e4ac045b2aa60955fad24',
|
||||
AAD: '1e0889016f67601c8ebea4943bc23ad6',
|
||||
CT : '8995ae2e6df3dbf96fac7b7137bae67feca5aa77d51d4a0a14d9c51e1da474ab',
|
||||
failed: false,
|
||||
},
|
||||
{
|
||||
key: 'b52c505a37d78eda5dd34f20c22540ea1b58963cf8e5bf8ffa85f9f2492505b4',
|
||||
IV : '516c33929df5a3284ff463d7',
|
||||
PT : '',
|
||||
AAD: '',
|
||||
CT : 'bdc1ac884d332457a1d2664f168c76f0',
|
||||
failed: false,
|
||||
},
|
||||
{
|
||||
key: '886cff5f3e6b8d0e1ad0a38fcdb26de97e8acbe79f6bed66959a598fa5047d65',
|
||||
IV : '3a8efa1cd74bbab5448f9945',
|
||||
PT : '',
|
||||
AAD: '519fee519d25c7a304d6c6aa1897ee1eb8c59655',
|
||||
CT : 'f6d47505ec96c98a42dc3ae719877b87',
|
||||
failed: false,
|
||||
},
|
||||
{
|
||||
key: '460fc864972261c2560e1eb88761ff1c992b982497bd2ac36c04071cbb8e5d99',
|
||||
IV : '8a4a16b9e210eb68bcb6f58d',
|
||||
PT : '99e4e926ffe927f691893fb79a96b067',
|
||||
AAD: '',
|
||||
CT : '133fc15751621b5f325c7ff71ce08324ec4e87e0cf74a13618d0b68636ba9fa7',
|
||||
failed: false,
|
||||
},
|
||||
].map(convertItem)
|
||||
|
||||
|
||||
|
||||
|
||||
test.each(tests)('encrypt(%#)', async (item) => {
|
||||
for (const item of tests) {
|
||||
|
||||
if(item.failed){
|
||||
expect(() => AES_GCM_Encrypt(item.key, item.IV, item.PT, item.AAD)).rejects.toThrow('Invalid');
|
||||
return;
|
||||
}
|
||||
const ct = await AES_GCM_Encrypt(item.key, item.IV, item.PT, item.AAD);
|
||||
expect(ct).toEqual(item.CT.buffer);
|
||||
}
|
||||
})
|
||||
|
||||
test.each(tests)('decrypt(%#)', async (item) => {
|
||||
for (const item of tests) {
|
||||
|
||||
if(item.failed){
|
||||
expect(() => AES_GCM_Decrypt(item.key, item.IV, item.CT, item.AAD)).rejects.toThrow('Invalid');
|
||||
return;
|
||||
}
|
||||
const pt = await AES_GCM_Decrypt(item.key, item.IV, item.CT, item.AAD);
|
||||
expect(pt).toEqual(item.PT.buffer);
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,61 @@
|
||||
import type { BufferSource } from "@sammo/util";
|
||||
|
||||
const subtle = globalThis.crypto.subtle;
|
||||
|
||||
|
||||
export async function AES_GCM_Encrypt(key: BufferSource | CryptoKey, iv: BufferSource, msg: BufferSource, aad?: BufferSource): Promise<ArrayBuffer> {
|
||||
const keyObj = key instanceof CryptoKey ? key : await subtle.importKey("raw", key, {
|
||||
name: "AES-GCM",
|
||||
}, false, ["encrypt"]);
|
||||
|
||||
const ciphertext = await subtle.encrypt({
|
||||
name: "AES-GCM",
|
||||
iv,
|
||||
additionalData: aad
|
||||
}, keyObj, msg);
|
||||
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
|
||||
export async function AES_GCM_Decrypt(key: BufferSource | CryptoKey, iv: BufferSource, ciphertext: BufferSource, aad?: BufferSource): Promise<ArrayBuffer> {
|
||||
const keyObj = key instanceof CryptoKey ? key : await subtle.importKey("raw", key, {
|
||||
name: "AES-GCM",
|
||||
}, false, ["decrypt"]);
|
||||
|
||||
const plaintext = await subtle.decrypt({
|
||||
name: "AES-GCM",
|
||||
iv: iv,
|
||||
additionalData: aad
|
||||
}, keyObj, ciphertext);
|
||||
//아마도 tag 검증 실패시 예외가 발생할 것으로 예상
|
||||
|
||||
return plaintext;
|
||||
}
|
||||
|
||||
export async function AES_CBC_Encrypt(key: BufferSource | CryptoKey, iv: BufferSource, msg: BufferSource): Promise<ArrayBuffer> {
|
||||
const keyObj = key instanceof CryptoKey ? key : await subtle.importKey("raw", key, {
|
||||
name: "AES-CBC",
|
||||
}, false, ["encrypt"]);
|
||||
|
||||
const ciphertext = await subtle.encrypt({
|
||||
name: "AES-CBC",
|
||||
iv,
|
||||
}, keyObj, msg);
|
||||
|
||||
return ciphertext;
|
||||
}
|
||||
|
||||
|
||||
export async function AES_CBC_Decrypt(key: BufferSource | CryptoKey, iv: BufferSource, ciphertext: BufferSource): Promise<ArrayBuffer> {
|
||||
const keyObj = key instanceof CryptoKey ? key : await subtle.importKey("raw", key, {
|
||||
name: "AES-CBC",
|
||||
}, false, ["decrypt"]);
|
||||
|
||||
const plaintext = await subtle.decrypt({
|
||||
name: "AES-CBC",
|
||||
iv: iv,
|
||||
}, keyObj, ciphertext);
|
||||
|
||||
return plaintext;
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { wrapBuffer } from "@sammo/util";
|
||||
import { ECDSA_sign, ECDSA_verify } from "./ECDSA.js";
|
||||
import { verifyKeyFromSignKey } from "./ECKey.js";
|
||||
import type { ECDHe_P384_PublicKey, ECDHe_P384_KeyPair, ECDSA_PKCS8_P384_SignKey, ECDHe_P384_PrivateKey, ECDHe_P384_PublicKeyInfo, ECDHe_P384_LiteKeyPair } from "./RawTypes.js";
|
||||
|
||||
const subtle = globalThis.crypto.subtle;
|
||||
|
||||
export const curveName: EcKeyGenParams | EcKeyImportParams = {
|
||||
name: 'ECDH',
|
||||
namedCurve: 'P-384'
|
||||
}
|
||||
|
||||
export async function genECDHeKey(signKey: ECDSA_PKCS8_P384_SignKey): Promise<ECDHe_P384_KeyPair> {
|
||||
const keyPairP = subtle.generateKey(curveName, true, ['deriveKey', 'deriveBits']);
|
||||
const verifyKeyP = verifyKeyFromSignKey(signKey);
|
||||
|
||||
const rawKeyPair = await keyPairP;
|
||||
const publicKey = wrapBuffer<ECDHe_P384_PublicKey>(await subtle.exportKey('spki', rawKeyPair.publicKey));
|
||||
const privateKey = wrapBuffer<ECDHe_P384_PrivateKey>(await subtle.exportKey('pkcs8', rawKeyPair.privateKey));
|
||||
|
||||
const signatureP = ECDSA_sign(signKey, privateKey);
|
||||
|
||||
return {
|
||||
publicInfo: {
|
||||
publicKey,
|
||||
verifyKey: await verifyKeyP,
|
||||
sign: await signatureP
|
||||
},
|
||||
privateKey,
|
||||
}
|
||||
}
|
||||
|
||||
export async function genECDHeLiteKey(): Promise<ECDHe_P384_LiteKeyPair> {
|
||||
const keyPair = await subtle.generateKey(curveName, true, ['deriveKey', 'deriveBits']);
|
||||
const publicKey = wrapBuffer<ECDHe_P384_PublicKey>(await subtle.exportKey('spki', keyPair.publicKey));
|
||||
const privateKey = wrapBuffer<ECDHe_P384_PrivateKey>(await subtle.exportKey('pkcs8', keyPair.privateKey));
|
||||
|
||||
return {
|
||||
publicKey,
|
||||
privateKey
|
||||
};
|
||||
}
|
||||
|
||||
export async function deriveKey(other: ECDHe_P384_PublicKeyInfo, me: ECDHe_P384_LiteKeyPair | ECDHe_P384_KeyPair, keySizeBit?: number): Promise<ArrayBuffer>;
|
||||
export async function deriveKey(other: ECDHe_P384_PublicKeyInfo | ECDHe_P384_PublicKey, me: ECDHe_P384_KeyPair, keySizeBit?: number): Promise<ArrayBuffer>;
|
||||
|
||||
export async function deriveKey(other: ECDHe_P384_PublicKeyInfo | ECDHe_P384_PublicKey, me: ECDHe_P384_KeyPair | ECDHe_P384_LiteKeyPair, keySizeBit = 256): Promise<ArrayBuffer> {
|
||||
|
||||
const rawPublicKey = other instanceof Uint8Array ? other : other.publicKey;
|
||||
const rawPrivateKey = me.privateKey;
|
||||
const publicKeyP = subtle.importKey('spki', rawPublicKey, curveName, true, ['deriveBits']);
|
||||
const privateKeyP = subtle.importKey('pkcs8', rawPrivateKey, curveName, true, ['deriveBits']);
|
||||
|
||||
if (!(other instanceof Uint8Array) && !(await ECDSA_verify(other.verifyKey, other.publicKey, other.sign))) {
|
||||
throw new Error("Invalid signature");
|
||||
}
|
||||
|
||||
return await subtle.deriveBits({
|
||||
name: 'ECDH',
|
||||
public: await publicKeyP
|
||||
|
||||
}, await privateKeyP, keySizeBit);
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import { AES_GCM_Decrypt, AES_GCM_Encrypt } from "./AES.js";
|
||||
import { deriveKey } from "./ECDHe.js";
|
||||
import type { ECDHe_P384_KeyPair, ECDHe_P384_LiteKeyPair, ECDHe_P384_PublicKey, ECDHe_P384_PublicKeyInfo } from "./RawTypes.js";
|
||||
|
||||
export type EncryptedItem = {
|
||||
ct: Uint8Array;
|
||||
}
|
||||
|
||||
const keySizeBit = 256;
|
||||
|
||||
export async function ECDHe_AES_GCM_Encrypt(other: ECDHe_P384_PublicKeyInfo, me: ECDHe_P384_LiteKeyPair | ECDHe_P384_KeyPair, iv: BufferSource, msg: BufferSource, aad?: BufferSource): Promise<ArrayBuffer>;
|
||||
export async function ECDHe_AES_GCM_Encrypt(other: ECDHe_P384_PublicKey, me: ECDHe_P384_KeyPair, iv: BufferSource, msg: BufferSource, aad?: BufferSource): Promise<ArrayBuffer>;
|
||||
export async function ECDHe_AES_GCM_Encrypt(other: ECDHe_P384_PublicKeyInfo, me: ECDHe_P384_LiteKeyPair, iv: BufferSource, msg: BufferSource, aad?: BufferSource): Promise<ArrayBuffer>;
|
||||
|
||||
export async function ECDHe_AES_GCM_Encrypt(other: ECDHe_P384_PublicKeyInfo | ECDHe_P384_PublicKey, me: ECDHe_P384_KeyPair | ECDHe_P384_LiteKeyPair, iv: BufferSource, msg: BufferSource, aad?: BufferSource): Promise<ArrayBuffer>{
|
||||
let key: ArrayBuffer;
|
||||
if('publicInfo' in me){
|
||||
key = await deriveKey(other, me, keySizeBit);
|
||||
}
|
||||
else if('publicKey' in other){
|
||||
key = await deriveKey(other, me, keySizeBit);
|
||||
}
|
||||
else{
|
||||
throw new Error("Invalid argument: may be trying to do ECDH, not ECDHe.");
|
||||
}
|
||||
|
||||
return AES_GCM_Encrypt(key, iv, msg, aad);
|
||||
}
|
||||
|
||||
export async function ECDHe_AES_GCM_Decrypt(other: ECDHe_P384_PublicKeyInfo, me: ECDHe_P384_LiteKeyPair | ECDHe_P384_KeyPair, iv: BufferSource, ciphertext: BufferSource, aad?: BufferSource): Promise<ArrayBuffer>;
|
||||
export async function ECDHe_AES_GCM_Decrypt(other: ECDHe_P384_PublicKey, me: ECDHe_P384_KeyPair, iv: BufferSource, ciphertext: BufferSource, aad?: BufferSource): Promise<ArrayBuffer>;
|
||||
export async function ECDHe_AES_GCM_Decrypt(other: ECDHe_P384_PublicKeyInfo, me: ECDHe_P384_LiteKeyPair, iv: BufferSource, ciphertext: BufferSource, aad?: BufferSource): Promise<ArrayBuffer>;
|
||||
|
||||
|
||||
export async function ECDHe_AES_GCM_Decrypt(other: ECDHe_P384_PublicKeyInfo | ECDHe_P384_PublicKey, me: ECDHe_P384_KeyPair | ECDHe_P384_LiteKeyPair, iv: BufferSource, ciphertext: BufferSource, aad?: BufferSource): Promise<ArrayBuffer>{
|
||||
let key: ArrayBuffer;
|
||||
if('publicInfo' in me){
|
||||
key = await deriveKey(other, me, keySizeBit);
|
||||
}
|
||||
else if('publicKey' in other){
|
||||
key = await deriveKey(other, me, keySizeBit);
|
||||
}
|
||||
else{
|
||||
throw new Error("Invalid argument: may be trying to do ECDH, not ECDHe.");
|
||||
}
|
||||
|
||||
return AES_GCM_Decrypt(key, iv, ciphertext, aad);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { ECDSA_sign_bson, ECDSA_verify_bson } from "./ECDSA.js";
|
||||
import { genKey } from "./ECKey.js";
|
||||
import { randomBytes } from "./utils.js";
|
||||
test('basic sign', async () => {
|
||||
const [signKey, verifyKey] = await genKey();
|
||||
|
||||
const misVerifyKey = Buffer.alloc(verifyKey.length);
|
||||
verifyKey.copy(misVerifyKey);
|
||||
misVerifyKey[misVerifyKey.length - 2] ^= 1; //1bit
|
||||
|
||||
const msg = randomBytes(100);
|
||||
const misMsg = Buffer.alloc(msg.length);
|
||||
msg.copy(misMsg);
|
||||
misMsg[1] ^= 1;
|
||||
|
||||
const signature = await ECDSA_sign_bson(signKey, msg);
|
||||
expect(signature.length).toEqual(96);
|
||||
|
||||
const misSignature = Buffer.alloc(signature.length);
|
||||
signature.copy(misSignature);
|
||||
misSignature[misSignature.length - 2] ^= 3;
|
||||
|
||||
const t0P = ECDSA_verify_bson(verifyKey, msg, signature);
|
||||
const f1P = ECDSA_verify_bson(misVerifyKey, msg, signature);
|
||||
const f2P = ECDSA_verify_bson(verifyKey, misMsg, signature);
|
||||
const f3P = ECDSA_verify_bson(verifyKey, msg, misSignature);
|
||||
|
||||
expect(await t0P).toEqual(true);
|
||||
expect(await f1P).toEqual(false);
|
||||
expect(await f2P).toEqual(false);
|
||||
expect(await f3P).toEqual(false);
|
||||
})
|
||||
@@ -0,0 +1,81 @@
|
||||
import { BSON } from "bson";
|
||||
import { type Bsonifiable, bsonify, type Jsonifiable, jsonify } from "@sammo/util";
|
||||
import { importSignKey, importVerifyKey } from "./ECKey.js";
|
||||
import type { ECDSASignature, ECDSA_PKCS8_P384_SignKey, ECDSA_P384_VerifyKey, ECDSASignatureBSON, ECDSASignatureJSON, ECDSASignatureRaw } from "./RawTypes.js";
|
||||
import { isBufferSource } from './types.js';
|
||||
import { isArray, isDate, isObject } from "lodash-es";
|
||||
|
||||
// 간편하게 저장하기 위해서 그냥! 매번 복잡한 일을 하도록 하자
|
||||
const subtle = globalThis.crypto.subtle;
|
||||
|
||||
const hashName: EcdsaParams = {
|
||||
name: 'ECDSA',
|
||||
hash: 'SHA-512'
|
||||
}
|
||||
|
||||
export async function ECDSA_sign(sign_key: ECDSA_PKCS8_P384_SignKey, msg: BufferSource): Promise<ECDSASignatureRaw> {
|
||||
const subtleSignKeyP = importSignKey(sign_key);
|
||||
return Buffer.from(await subtle.sign(hashName, await subtleSignKeyP, msg));
|
||||
}
|
||||
|
||||
|
||||
export async function ECDSA_sign_bson(sign_key: ECDSA_PKCS8_P384_SignKey, msg: Bsonifiable): Promise<ECDSASignatureBSON>
|
||||
export async function ECDSA_sign_bson(sign_key: ECDSA_PKCS8_P384_SignKey, msg: BufferSource): Promise<ECDSASignatureRaw>
|
||||
export async function ECDSA_sign_bson(sign_key: ECDSA_PKCS8_P384_SignKey, msg: Bsonifiable | BufferSource): Promise<ECDSASignatureRaw | ECDSASignatureBSON> {
|
||||
if (!isBufferSource(msg)) {
|
||||
msg = BSON.serialize(bsonify(msg as Record<string, string>));
|
||||
return await ECDSA_sign(sign_key, msg) as ECDSASignatureBSON;
|
||||
}
|
||||
return ECDSA_sign(sign_key, msg);
|
||||
}
|
||||
|
||||
export async function ECDSA_sign_json(sign_key: ECDSA_PKCS8_P384_SignKey, msg: Bsonifiable): Promise<ECDSASignatureJSON>
|
||||
export async function ECDSA_sign_json(sign_key: ECDSA_PKCS8_P384_SignKey, msg: BufferSource): Promise<ECDSASignatureRaw>
|
||||
export async function ECDSA_sign_json(sign_key: ECDSA_PKCS8_P384_SignKey, msg: Jsonifiable | BufferSource): Promise<ECDSASignatureRaw | ECDSASignatureJSON> {
|
||||
if (!isBufferSource(msg)) {
|
||||
msg = Buffer.from(JSON.stringify(jsonify(msg as Record<string, string>)), 'utf-8');
|
||||
return await ECDSA_sign(sign_key, msg) as ECDSASignatureJSON;
|
||||
}
|
||||
return ECDSA_sign(sign_key, msg);
|
||||
}
|
||||
|
||||
export async function ECDSA_verify(verify_key: ECDSA_P384_VerifyKey, msg: BufferSource, sign: ECDSASignature): Promise<boolean> {
|
||||
try {
|
||||
const subtleVerifyKeyP = importVerifyKey(verify_key);
|
||||
return await subtle.verify(hashName, await subtleVerifyKeyP, sign, msg);
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function ECDSA_verify_bson(verify_key: ECDSA_P384_VerifyKey, msg: Bsonifiable, sign: ECDSASignatureBSON): Promise<boolean>;
|
||||
export async function ECDSA_verify_bson(verify_key: ECDSA_P384_VerifyKey, msg: BufferSource, sign: ECDSASignatureRaw): Promise<boolean>;
|
||||
export async function ECDSA_verify_bson(verify_key: ECDSA_P384_VerifyKey, msg: Bsonifiable | BufferSource, sign: ECDSASignatureBSON | ECDSASignatureRaw): Promise<boolean> {
|
||||
|
||||
try {
|
||||
if (!isBufferSource(msg)) {
|
||||
msg = BSON.serialize(bsonify(msg as Record<string, string>));
|
||||
}
|
||||
return ECDSA_verify(verify_key, msg, sign);
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function ECDSA_verify_json(verify_key: ECDSA_P384_VerifyKey, msg: Bsonifiable, sign: ECDSASignatureJSON): Promise<boolean>;
|
||||
export async function ECDSA_verify_json(verify_key: ECDSA_P384_VerifyKey, msg: BufferSource, sign: ECDSASignatureRaw): Promise<boolean>;
|
||||
export async function ECDSA_verify_json(verify_key: ECDSA_P384_VerifyKey, msg: Jsonifiable | BufferSource, sign: ECDSASignatureJSON | ECDSASignatureRaw): Promise<boolean> {
|
||||
|
||||
try {
|
||||
if (!isBufferSource(msg)) {
|
||||
const x = JSON.stringify(jsonify(msg as Record<string, string>));
|
||||
msg = Buffer.from(x, 'utf-8');
|
||||
}
|
||||
return ECDSA_verify(verify_key, msg, sign);
|
||||
}
|
||||
catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
import { genKey, verifyKeyFromSignKey, pemFromVerifyKey, importVerifyKey, importSignKey, pemFromSignKey } from "./ECKey.js";
|
||||
import { TypeLength } from "./TypeLength.js";
|
||||
import { decodePEM } from "./utils.js";
|
||||
|
||||
test('gen_key', async () => {
|
||||
const [signKey, verifyKey] = await genKey();
|
||||
|
||||
expect(signKey.length).toEqual(TypeLength.ECDSA_PKCS8_P384_SignKey);
|
||||
expect(verifyKey.length).toEqual(TypeLength.ECDSA_SPKI_P384_VerifyKey);
|
||||
|
||||
const pemVerifyKey = await pemFromVerifyKey(verifyKey);
|
||||
const verifyKey2 = decodePEM(pemVerifyKey, 'PUBLIC KEY')[0];
|
||||
expect(verifyKey).toEqual(verifyKey2);
|
||||
|
||||
});
|
||||
|
||||
test('convert', async () => {
|
||||
const [signKey, verifyKey] = await genKey();
|
||||
|
||||
const newVerifyKey = await verifyKeyFromSignKey(signKey);
|
||||
|
||||
expect(verifyKey).toEqual(newVerifyKey);
|
||||
})
|
||||
|
||||
test('import sign_key', async() => {
|
||||
const [signKey, verifyKey] = await genKey();
|
||||
|
||||
const pemSignKey = pemFromSignKey(signKey);
|
||||
const signKey2 = decodePEM(pemSignKey, 'EC PRIVATE KEY')[0];
|
||||
|
||||
const signKeyObj = await importSignKey(signKey, true);
|
||||
const signKeyObj2 = await importSignKey(signKey2, true);
|
||||
|
||||
expect(signKeyObj).toEqual(signKeyObj2);
|
||||
})
|
||||
@@ -0,0 +1,62 @@
|
||||
|
||||
import type { ECDSA_PKCS8_P384_SignKey, ECDSA_P384_VerifyKey } from "./RawTypes.js";
|
||||
import { encodePEM } from "./utils.js";
|
||||
|
||||
// 간편하게 저장하기 위해서 그냥! 매번 복잡한 일을 하도록 하자
|
||||
|
||||
const subtle = globalThis.crypto.subtle;
|
||||
|
||||
export const curveName: EcKeyGenParams | EcKeyImportParams = {
|
||||
name: 'ECDSA',
|
||||
namedCurve: 'P-384'
|
||||
}
|
||||
|
||||
export async function importSignKey(signKey: ECDSA_PKCS8_P384_SignKey, allowExport?: boolean): Promise<CryptoKey> {
|
||||
return await subtle.importKey('pkcs8', signKey, curveName, allowExport ?? false, ['sign']);
|
||||
}
|
||||
|
||||
export async function importVerifyKeyRaw(verifyKey: ECDSA_P384_VerifyKey, allowExport?: boolean): Promise<CryptoKey> {
|
||||
return await subtle.importKey('raw', verifyKey, curveName, allowExport ?? false, ['verify'])
|
||||
}
|
||||
|
||||
export async function exportVerifyKeyRaw(verifyKey: ECDSA_P384_VerifyKey): Promise<Buffer>{
|
||||
const key = await importVerifyKeyRaw(verifyKey);
|
||||
return Buffer.from(await subtle.exportKey('raw', key));
|
||||
}
|
||||
|
||||
export async function importVerifyKey(verifyKey: ECDSA_P384_VerifyKey, allowExport?: boolean): Promise<CryptoKey> {
|
||||
return await subtle.importKey('spki', verifyKey, curveName, allowExport ?? false, ['verify'])
|
||||
}
|
||||
|
||||
|
||||
export async function genKey(): Promise<[ECDSA_PKCS8_P384_SignKey, ECDSA_P384_VerifyKey]> {
|
||||
const keyPair = await subtle.generateKey(curveName, true, ['sign', 'verify']);
|
||||
const signKey = subtle.exportKey('pkcs8', keyPair.privateKey);
|
||||
const verifyKey = subtle.exportKey('spki', keyPair.publicKey);
|
||||
return [Buffer.from(await signKey), Buffer.from(await verifyKey)];
|
||||
}
|
||||
|
||||
export async function pemFromVerifyKey(verifyKey: ECDSA_P384_VerifyKey): Promise<string> {
|
||||
const subtleVerifyKey = await importVerifyKey(verifyKey, true);
|
||||
const spkiRaw = await subtle.exportKey('spki', subtleVerifyKey)
|
||||
return encodePEM(Buffer.from(spkiRaw), 'PUBLIC KEY');
|
||||
}
|
||||
|
||||
export function pemFromSignKey(signKey: ECDSA_PKCS8_P384_SignKey): string {
|
||||
return encodePEM(signKey, 'EC PRIVATE KEY');
|
||||
}
|
||||
|
||||
export async function verifyKeyFromSignKey(signKey: ECDSA_PKCS8_P384_SignKey): Promise<ECDSA_P384_VerifyKey> {
|
||||
const subtleSignKey = await importSignKey(signKey, true);
|
||||
const jwk = await subtle.exportKey('jwk', subtleSignKey);
|
||||
delete jwk.d;
|
||||
delete jwk.dp;
|
||||
delete jwk.dq;
|
||||
delete jwk.q;
|
||||
delete jwk.qi;
|
||||
jwk.key_ops = ["verify"];
|
||||
|
||||
const subtleVerifyKey = await subtle.importKey('jwk', jwk, curveName, true, ['verify']);
|
||||
const verifyKey = await subtle.exportKey('spki', subtleVerifyKey);
|
||||
return Buffer.from(verifyKey);
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
import type { RNG } from "./RNG.js";
|
||||
import { sha512 } from './SHA2.js';
|
||||
import { convertBytesLikeToUint8Array } from "@sammo/util/converter";
|
||||
import type { BytesLike } from "@sammo/util";
|
||||
import { delay } from "@sammo/util";
|
||||
|
||||
const maxRngSupportBit = 53;
|
||||
const maxInt = 0x1f_ffff_ffff_ffff; // NOTE: b 0, 10000110011, 11...11
|
||||
const maxIntMore1 = 0x20_0000_0000_0000n; //NOTE: b 0, 10000110100, 00...00
|
||||
const maxIntMore1f = Number(maxIntMore1);
|
||||
export const bufferByteSize = 512 / 8; //SHA512
|
||||
|
||||
const intBitMapMask = new Map([
|
||||
[0x1n, 1],
|
||||
[0x3n, 2],
|
||||
[0x7n, 3],
|
||||
[0xfn, 4],
|
||||
[0x1fn, 5],
|
||||
[0x3fn, 6],
|
||||
[0x7fn, 7],
|
||||
[0xffn, 8],
|
||||
[0x1ffn, 9],
|
||||
[0x3ffn, 10],
|
||||
[0x7ffn, 11],
|
||||
[0xfffn, 12],
|
||||
[0x1fffn, 13],
|
||||
[0x3fffn, 14],
|
||||
[0x7fffn, 15],
|
||||
[0xffffn, 16],
|
||||
[0x1ffffn, 17],
|
||||
[0x3ffffn, 18],
|
||||
[0x7ffffn, 19],
|
||||
[0xfffffn, 20],
|
||||
[0x1fffffn, 21],
|
||||
[0x3fffffn, 22],
|
||||
[0x7fffffn, 23],
|
||||
[0xffffffn, 24],
|
||||
[0x1ffffffn, 25],
|
||||
[0x3ffffffn, 26],
|
||||
[0x7ffffffn, 27],
|
||||
[0xfffffffn, 28],
|
||||
[0x1fffffffn, 29],
|
||||
[0x3fffffffn, 30],
|
||||
[0x7fffffffn, 31],
|
||||
[0xffffffffn, 32],
|
||||
[0x1ffffffffn, 33],
|
||||
[0x3ffffffffn, 34],
|
||||
[0x7ffffffffn, 35],
|
||||
[0xfffffffffn, 36],
|
||||
[0x1fffffffffn, 37],
|
||||
[0x3fffffffffn, 38],
|
||||
[0x7fffffffffn, 39],
|
||||
[0xffffffffffn, 40],
|
||||
[0x1ffffffffffn, 41],
|
||||
[0x3ffffffffffn, 42],
|
||||
[0x7ffffffffffn, 43],
|
||||
[0xfffffffffffn, 44],
|
||||
[0x1fffffffffffn, 45],
|
||||
[0x3fffffffffffn, 46],
|
||||
[0x7fffffffffffn, 47],
|
||||
[0xffffffffffffn, 48],
|
||||
[0x1ffffffffffffn, 49],
|
||||
[0x3ffffffffffffn, 50],
|
||||
[0x7ffffffffffffn, 51],
|
||||
[0xfffffffffffffn, 52],
|
||||
[0x1fffffffffffffn, 53],
|
||||
]);
|
||||
|
||||
function calcBitMask(n: bigint): bigint {
|
||||
n |= n >> 1n;
|
||||
n |= n >> 2n;
|
||||
n |= n >> 4n;
|
||||
n |= n >> 8n;
|
||||
n |= n >> 16n;
|
||||
n |= n >> 32n;
|
||||
|
||||
return n;
|
||||
}
|
||||
export class LiteHashDRBG implements RNG {
|
||||
|
||||
protected buffer!: ArrayBuffer;
|
||||
protected bufferIdx!: number;
|
||||
protected hq: DataView;
|
||||
protected hqIdxPos: number;
|
||||
|
||||
protected ready: Promise<void>;
|
||||
|
||||
public constructor(protected seed: BytesLike, protected stateIdx = 0, bufferIdx = 0) {
|
||||
if (bufferIdx < 0) {
|
||||
throw new Error(`bufferIdx ${bufferIdx} < 0`);
|
||||
}
|
||||
if (bufferIdx >= bufferByteSize) {
|
||||
throw new Error(`bufferidx ${bufferIdx} >= ${bufferByteSize}`);
|
||||
}
|
||||
if (stateIdx < 0) {
|
||||
throw new Error(`stateIdx ${stateIdx} < 0`);
|
||||
}
|
||||
|
||||
const seedU8 = convertBytesLikeToUint8Array(seed);
|
||||
const hqBuffer = new ArrayBuffer(seedU8.byteLength + 4);
|
||||
const hqU8 = new Uint8Array(hqBuffer);
|
||||
|
||||
hqU8.set(seedU8, 0);
|
||||
this.hq = new DataView(hqBuffer);
|
||||
this.hqIdxPos = seedU8.byteLength;
|
||||
|
||||
this.ready = this.genNextBlock();
|
||||
this.bufferIdx = bufferIdx;
|
||||
}
|
||||
|
||||
protected async genNextBlock(): Promise<void> {
|
||||
this.bufferIdx = 0;
|
||||
this.hq.setUint32(this.hqIdxPos, this.stateIdx, true);
|
||||
this.stateIdx += 1;
|
||||
const digest = await sha512(this.hq.buffer);
|
||||
this.buffer = digest;
|
||||
}
|
||||
|
||||
public getMaxInt(): number {
|
||||
return maxInt;
|
||||
}
|
||||
|
||||
public async nextBytes(bytes: number, baseBytes?: number): Promise<Uint8Array> {
|
||||
bytes |= 0;
|
||||
if (bytes <= 0) {
|
||||
throw new Error(`${bytes} <= 0`);
|
||||
}
|
||||
|
||||
const ticket = this.ready;
|
||||
|
||||
let waiter: Promise<Uint8Array | undefined> = Promise.resolve(undefined);
|
||||
|
||||
let nextBlockWait: (() => void) | null = (() => { throw 'something wrong'; });
|
||||
|
||||
this.ready = new Promise((resolve, reject) => {
|
||||
waiter = (async () => {
|
||||
await ticket;
|
||||
nextBlockWait = resolve;
|
||||
|
||||
if (this.bufferIdx + bytes <= bufferByteSize) {
|
||||
if (baseBytes === undefined || bytes >= baseBytes) {
|
||||
const result = this.buffer.slice(this.bufferIdx, this.bufferIdx + bytes);
|
||||
this.bufferIdx += bytes;
|
||||
if (this.bufferIdx === bufferByteSize) {
|
||||
nextBlockWait = null;
|
||||
this.genNextBlock().then(resolve, reject);
|
||||
}
|
||||
return new Uint8Array(result);
|
||||
}
|
||||
|
||||
const resultBuffer = new ArrayBuffer(Math.max(bytes, baseBytes ?? 0));
|
||||
const result = new Uint8Array(resultBuffer);
|
||||
result.set(new Uint8Array(this.buffer, this.bufferIdx, bytes));
|
||||
this.bufferIdx += bytes;
|
||||
if (this.bufferIdx === bufferByteSize) {
|
||||
nextBlockWait = null;
|
||||
this.genNextBlock().then(resolve, reject);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const resultBuffer = new ArrayBuffer(baseBytes ? Math.max(bytes, baseBytes) : bytes);
|
||||
const result = new Uint8Array(resultBuffer);
|
||||
|
||||
result.set(new Uint8Array(this.buffer, this.bufferIdx));
|
||||
let offset = bufferByteSize - this.bufferIdx;
|
||||
let remain = bytes - offset;
|
||||
|
||||
while (remain > bufferByteSize) {
|
||||
await this.genNextBlock();
|
||||
result.set(new Uint8Array(this.buffer), offset);
|
||||
offset += bufferByteSize;
|
||||
remain -= bufferByteSize;
|
||||
}
|
||||
|
||||
if (remain === 0) {
|
||||
nextBlockWait = null;
|
||||
this.genNextBlock().then(resolve, reject);
|
||||
return result;
|
||||
}
|
||||
|
||||
await this.genNextBlock();
|
||||
result.set(new Uint8Array(this.buffer, 0, remain), offset);
|
||||
this.bufferIdx = remain;
|
||||
return result;
|
||||
})();
|
||||
|
||||
});
|
||||
|
||||
//이 코드를 통해 Promise 내부가 실행된다
|
||||
await delay(0);
|
||||
|
||||
const nextBlock = await waiter;
|
||||
if (nextBlockWait) {
|
||||
nextBlockWait();
|
||||
}
|
||||
return nextBlock as Uint8Array;
|
||||
}
|
||||
|
||||
public async nextBits(bits: number, baseBytes?: number): Promise<Uint8Array> {
|
||||
await this.ready;
|
||||
|
||||
bits |= 0;
|
||||
const bytes = (bits + 7) >> 3;
|
||||
const headBits = bits & 0x7;
|
||||
|
||||
const result = await this.nextBytes(bytes, baseBytes);
|
||||
if (headBits === 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result[bytes - 1] &= 0xff >> (8 - headBits);
|
||||
return result;
|
||||
}
|
||||
|
||||
protected async _nextInt(bits: number): Promise<bigint> {
|
||||
const buffer = await this.nextBits(bits, 8);
|
||||
const dataView = new DataView(buffer.buffer);
|
||||
return dataView.getBigUint64(0, true);
|
||||
}
|
||||
|
||||
public async nextInt(max?: number): Promise<number> {
|
||||
if (max === undefined || max === maxInt) {
|
||||
return Number(await this._nextInt(maxRngSupportBit));
|
||||
}
|
||||
if (max > maxInt) {
|
||||
throw new Error('Over max int');
|
||||
}
|
||||
if (max === 0) {
|
||||
return 0;
|
||||
}
|
||||
if (max < 0) {
|
||||
return -this.nextInt(-max);
|
||||
}
|
||||
|
||||
const mask = calcBitMask(BigInt(max));
|
||||
const bits = intBitMapMask.get(mask) as number;
|
||||
|
||||
let n = Number(this._nextInt(bits));
|
||||
while (n > max) {
|
||||
n = Number(this._nextInt(bits));
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
public async nextFloat1(): Promise<number> {
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
while (true) {
|
||||
const nInt = await this._nextInt(maxRngSupportBit + 1);
|
||||
if (nInt < maxIntMore1) {
|
||||
return Number(nInt) / maxIntMore1f;
|
||||
}
|
||||
if (nInt === maxIntMore1) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static build(seed: BytesLike, stateIdx = 0): LiteHashDRBG {
|
||||
return new LiteHashDRBG(seed, stateIdx);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
const subtle = globalThis.crypto.subtle;
|
||||
|
||||
export type ValidEncAlg = 'AES256-GCM' | 'AES128-GCM' | 'AES256-CBC' | 'AES128-CBC';
|
||||
|
||||
const encAlgMap: Record<ValidEncAlg, AesDerivedKeyParams> = {
|
||||
'AES256-GCM': {
|
||||
name: 'AES-GCM',
|
||||
length: 256,
|
||||
},
|
||||
'AES128-GCM' : {
|
||||
name: 'AES-GCM',
|
||||
length: 128,
|
||||
},
|
||||
'AES256-CBC' : {
|
||||
name: 'AES-CBC',
|
||||
length: 256,
|
||||
},
|
||||
'AES128-CBC' : {
|
||||
name: 'AES-CBC',
|
||||
length: 128,
|
||||
},
|
||||
}
|
||||
|
||||
export async function PBKDF2_SHA512(password: string, salt: BufferSource, alg: ValidEncAlg = 'AES256-GCM', iterations = 600000): Promise<CryptoKey> {
|
||||
const param: Pbkdf2Params = {
|
||||
name: 'PBKDF2',
|
||||
hash: 'SHA-512',
|
||||
salt: salt,
|
||||
iterations,
|
||||
};
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const baseKey = await subtle.importKey(
|
||||
"raw",
|
||||
encoder.encode(password),
|
||||
{ name: "PBKDF2" },
|
||||
false,
|
||||
["deriveBits", "deriveKey"]
|
||||
);
|
||||
|
||||
return await subtle.deriveKey(
|
||||
param,
|
||||
baseKey,
|
||||
encAlgMap[alg],
|
||||
true,
|
||||
["encrypt", "decrypt"]
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export interface RNG {
|
||||
|
||||
/**
|
||||
* nextInt()가 반환 가능한 최댓값
|
||||
*/
|
||||
getMaxInt(): number;
|
||||
|
||||
nextBytes(bytes: number): Promise<Uint8Array>;
|
||||
nextBits(bits: number): Promise<Uint8Array>;
|
||||
|
||||
nextInt(max?: number): Promise<number>;
|
||||
nextFloat1(): Promise<number>;
|
||||
}
|
||||
@@ -0,0 +1,144 @@
|
||||
import type { RNG } from './RNG.js';
|
||||
|
||||
export class RandUtil {
|
||||
constructor(protected rng: RNG) {
|
||||
|
||||
}
|
||||
|
||||
public nextFloat1(): Promise<number> {
|
||||
return this.rng.nextFloat1();
|
||||
}
|
||||
|
||||
public async nextRange(min: number, max: number): Promise<number> {
|
||||
const range = max - min;
|
||||
return await this.nextFloat1() * (range) + min;
|
||||
}
|
||||
|
||||
public async nextRangeInt(min: number, max: number): Promise<number> {
|
||||
const range = max - min;
|
||||
return await this.rng.nextInt(range) + min;
|
||||
}
|
||||
|
||||
public nextInt(max?: number): Promise<number> {
|
||||
return this.rng.nextInt(max);
|
||||
}
|
||||
|
||||
public async nextBit(): Promise<boolean> {
|
||||
const view = new DataView(await this.rng.nextBits(1) as ArrayBufferLike);
|
||||
return view.getUint8(0) != 0;
|
||||
}
|
||||
|
||||
public async nextBool(prob = 0.5): Promise<boolean> {
|
||||
if (prob >= 1) {
|
||||
return true;
|
||||
}
|
||||
if (prob === 0.5){
|
||||
return this.nextBit();
|
||||
}
|
||||
if (prob <= 0){
|
||||
return false;
|
||||
}
|
||||
return await this.nextFloat1() < prob;
|
||||
}
|
||||
|
||||
public async shuffle<T>(srcArray: T[]): Promise<T[]> {
|
||||
const cnt = srcArray.length;
|
||||
if(cnt === 0){
|
||||
return [];
|
||||
}
|
||||
if (cnt > this.rng.getMaxInt()) {
|
||||
throw 'Invalid random int range';
|
||||
}
|
||||
|
||||
const result: T[] = Array.from(srcArray);
|
||||
for (let srcIdx = 0; srcIdx < cnt; srcIdx += 1) {
|
||||
const destIdx = await this.rng.nextInt(cnt - srcIdx - 1) + srcIdx;
|
||||
if(srcIdx === destIdx){
|
||||
continue;
|
||||
}
|
||||
[result[srcIdx], result[destIdx]] = [result[destIdx], result[srcIdx]];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//Object는 integer key에 예외가 있어 shuffleAssoc은 없음
|
||||
|
||||
public async choice<T>(items: T[] | Record<string | number, T> | Set<T>): Promise<T> {
|
||||
if (items instanceof Array) {
|
||||
if(items.length === 0){
|
||||
throw new Error('Empty items');
|
||||
}
|
||||
const idx = await this.rng.nextInt(items.length - 1);
|
||||
return items[idx];
|
||||
}
|
||||
|
||||
if (items instanceof Set) {
|
||||
return this.choice(Array.from(items.values()));
|
||||
}
|
||||
|
||||
return items[await this.choice(Array.from(Object.keys(items)))];
|
||||
}
|
||||
|
||||
public async choiceUsingWeight(items: Record<string | number, number>): Promise<string | number> {
|
||||
if(Object.keys(items).length === 0){
|
||||
throw new Error('Empty items');
|
||||
}
|
||||
let sum = 0;
|
||||
for (const value of Object.values(items)) {
|
||||
if (value <= 0) {
|
||||
continue;
|
||||
}
|
||||
sum += value;
|
||||
}
|
||||
|
||||
let rd = await this.nextFloat1() * sum;
|
||||
|
||||
for (const [item, value] of Object.entries(items)) {
|
||||
if (value <= 0) {
|
||||
if (rd <= 0) {
|
||||
return item;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rd <= value) {
|
||||
return item;
|
||||
}
|
||||
rd -= value;
|
||||
}
|
||||
|
||||
throw new Error('Unreacheable');
|
||||
}
|
||||
|
||||
public async choiceUsingWeightPair<T>(items: [T, number][]): Promise<T> {
|
||||
if(items.length === 0){
|
||||
throw new Error('Empty items');
|
||||
}
|
||||
let sum = 0;
|
||||
for (const [, value] of items) {
|
||||
if (value <= 0) {
|
||||
continue;
|
||||
}
|
||||
sum += value;
|
||||
}
|
||||
|
||||
let rd = await this.nextFloat1() * sum;
|
||||
|
||||
for (const [item, value] of items) {
|
||||
if (value <= 0) {
|
||||
if (rd <= 0) {
|
||||
return item;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rd <= value) {
|
||||
return item;
|
||||
}
|
||||
rd -= value;
|
||||
}
|
||||
|
||||
throw new Error('Unreacheable');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import type { WrappedBuffer } from "@sammo/util";
|
||||
|
||||
export interface ECDSA_PKCS8_P384_SignKey extends WrappedBuffer{
|
||||
_w_type?: "ECDSA_PKCS8_P384_SignKey";
|
||||
}
|
||||
|
||||
export interface ECDSA_P384_VerifyKey extends WrappedBuffer{
|
||||
_w_type?: "ECDSA_P384_VerifyKey";
|
||||
}
|
||||
|
||||
export interface ECDSASignatureRaw extends WrappedBuffer{
|
||||
_w_type?: "ECDSASignature";
|
||||
}
|
||||
|
||||
export interface ECDSASignatureBSON extends WrappedBuffer{
|
||||
_w_type?: "ECDSASignatureBSON";
|
||||
}
|
||||
|
||||
export interface ECDSASignatureJSON extends WrappedBuffer{
|
||||
_w_type?: "ECDSASignatureJSON";
|
||||
}
|
||||
|
||||
export type ECDSASignature = ECDSASignatureRaw | ECDSASignatureBSON | ECDSASignatureJSON;
|
||||
|
||||
export interface ECDHe_P384_PublicKey extends WrappedBuffer{
|
||||
_w_type?: "ECDHe_P384_PublicKey";
|
||||
}
|
||||
|
||||
export interface ECDHe_P384_PrivateKey extends WrappedBuffer{
|
||||
_w_type?: "ECDHe_P384_PrivateKey";
|
||||
}
|
||||
|
||||
export interface ECDHe_P384_LiteKeyPair {
|
||||
publicKey: ECDHe_P384_PublicKey;
|
||||
privateKey: ECDHe_P384_PrivateKey;
|
||||
}
|
||||
|
||||
export interface ECDHe_P384_PublicKeyInfo {
|
||||
publicKey: ECDHe_P384_PublicKey;
|
||||
verifyKey: ECDSA_P384_VerifyKey;
|
||||
sign: ECDSASignatureRaw; //sign of publicKey
|
||||
}
|
||||
export interface ECDHe_P384_KeyPair{
|
||||
publicInfo: ECDHe_P384_PublicKeyInfo;
|
||||
privateKey: ECDHe_P384_PrivateKey;
|
||||
}
|
||||
|
||||
export {
|
||||
TypeLength
|
||||
} from "./TypeLength.js";
|
||||
@@ -0,0 +1,10 @@
|
||||
import { sha256, sha512 } from "./SHA2.js";
|
||||
|
||||
test('sha2', async () => {
|
||||
const msg = Buffer.from('hello');
|
||||
const answer256 = Buffer.from('2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824', 'hex');
|
||||
const answer512 = Buffer.from('9B71D224BD62F3785D96D46AD3EA3D73319BFBC2890CAADAE2DFF72519673CA72323C3D99BA5C11D7C7ACC6E14B8C5DA0C4663475C2E5C3ADEF46F73BCDEC043', 'hex');
|
||||
|
||||
expect(await sha256(msg)).toEqual(answer256.buffer);
|
||||
expect(await sha512(msg)).toEqual(answer512.buffer);
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import { isBufferSource } from "./types.js";
|
||||
import { BSON } from "bson";
|
||||
import { type Bsonifiable, bsonify } from "@sammo/util";
|
||||
import { isArray, isDate, isObject } from "lodash-es";
|
||||
const subtle = globalThis.crypto.subtle;
|
||||
|
||||
export async function sha256(msg: Bsonifiable | BufferSource): Promise<ArrayBuffer> {
|
||||
if (!isBufferSource(msg)) {
|
||||
msg = BSON.serialize(bsonify(msg));
|
||||
}
|
||||
return await subtle.digest('SHA-256', msg);
|
||||
}
|
||||
|
||||
export async function sha512(msg: Bsonifiable | BufferSource): Promise<ArrayBuffer> {
|
||||
if (!isBufferSource(msg)) {
|
||||
msg = BSON.serialize(bsonify(msg));
|
||||
}
|
||||
return await subtle.digest('SHA-512', msg);
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export enum TypeLength {
|
||||
//ECDSA P384 서명
|
||||
ECDSA_PKCS8_P384_SignKey = 185,
|
||||
ECDSA_SPKI_P384_VerifyKey = 120,
|
||||
ECDSASignature = 96,
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
import { InvalidArgument } from '@sammo/util';
|
||||
|
||||
export class InvalidVType extends InvalidArgument {
|
||||
public override name = 'InvalidVType';
|
||||
}
|
||||
|
||||
export class InvalidArgumentBufferSize extends InvalidArgument {
|
||||
public override name = 'InvalidArgumentBufferSize';
|
||||
}
|
||||
|
||||
export class RuntimeError extends Error {
|
||||
public override name = 'RuntimeError';
|
||||
constructor(public override message: string = '') {
|
||||
super(message);
|
||||
}
|
||||
override toString(): string {
|
||||
if (this.message) {
|
||||
return this.name + ': ' + this.message;
|
||||
}
|
||||
else {
|
||||
return this.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class NotNullExpected extends RuntimeError {
|
||||
public override name = 'NotNullExpected';
|
||||
}
|
||||
|
||||
export class PrivilegedGenViolation extends RuntimeError {
|
||||
public override name = 'PrivilegedGenViolation';
|
||||
}
|
||||
|
||||
export type MergeableBuffer = MergeableBuffer[] | Buffer;
|
||||
|
||||
export function calcMergeableBuffer(item: MergeableBuffer): number {
|
||||
if (item instanceof Buffer) {
|
||||
return item.byteLength;
|
||||
}
|
||||
let bufferSize = 0;
|
||||
for (const subItem of item) {
|
||||
bufferSize += calcMergeableBuffer(subItem);
|
||||
}
|
||||
return bufferSize;
|
||||
}
|
||||
|
||||
export function mergeBuffer(...buffers: MergeableBuffer[]): Buffer {
|
||||
if (buffers.length == 0) {
|
||||
return Buffer.alloc(0);
|
||||
}
|
||||
if (buffers.length == 1) {
|
||||
const item = buffers[0];
|
||||
if (item instanceof Buffer) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
const fillBuffer = function (item: MergeableBuffer, bufferIdx: number): number {
|
||||
if (item instanceof Buffer) {
|
||||
result.set(item, bufferIdx);
|
||||
bufferIdx += item.byteLength;
|
||||
return bufferIdx;
|
||||
}
|
||||
for (const subItem of item) {
|
||||
bufferIdx = fillBuffer(subItem, bufferIdx);
|
||||
}
|
||||
return bufferIdx;
|
||||
}
|
||||
|
||||
const bufferSize = calcMergeableBuffer(buffers);
|
||||
const result = Buffer.alloc(bufferSize);
|
||||
|
||||
fillBuffer(buffers, 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
type ErrType<T> = { new(msg?: string): T }
|
||||
type Nullable<T> = T | null | undefined
|
||||
|
||||
export function unwrap<T>(result: Nullable<T>): T {
|
||||
if (result === null || result === undefined) {
|
||||
throw new NotNullExpected();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function unwrap_err<T, ErrT extends Error>(result: Nullable<T>, errType: ErrType<ErrT>, errMsg?: string): T {
|
||||
if (result === null || result === undefined) {
|
||||
throw new errType(errMsg);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export * as AES from './AES.js';
|
||||
export * as ECDSA from './ECDSA.js';
|
||||
export * as ECKey from './ECKey.js';
|
||||
export * as PBKDF2 from './PBKDF2.js';
|
||||
export * as SHA2 from './SHA2.js';
|
||||
export * as ECDHe from './ECDHe.js';
|
||||
export * as ECDHe_AES from './ECDHe_AES.js';
|
||||
|
||||
export * as RawTypes from './RawTypes.js';
|
||||
|
||||
export * from './utils.js';
|
||||
export * from './types.js';
|
||||
export { TypeLength } from './TypeLength.js';
|
||||
|
||||
export * from "./LiteHashDRBG.js"
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
export function isBufferSource(obj: unknown): obj is BufferSource {
|
||||
if (obj instanceof ArrayBuffer){
|
||||
return true;
|
||||
}
|
||||
if('SharedArrayBuffer' in globalThis && obj instanceof SharedArrayBuffer){
|
||||
return true;
|
||||
}
|
||||
if (ArrayBuffer.isView(obj)){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import type { WrappedBuffer } from "@sammo/util";
|
||||
|
||||
const crypto = globalThis.crypto;
|
||||
|
||||
export function randomBytes(length: number): Buffer {
|
||||
const buffer = Buffer.alloc(length);
|
||||
crypto.getRandomValues(buffer);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
//TODO: 필요할때마다 확장
|
||||
export type ValidPEMType = 'PUBLIC KEY' | 'EC PRIVATE KEY' | 'CERTIFICATE';
|
||||
|
||||
/**
|
||||
* PEM string에 내부 타입으로 WrappedBuffer를 보관한 형태
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
export type PEMString<T extends Buffer, S extends ValidPEMType> = string & {
|
||||
/** 타입구분자. 항상 undefined일 것이다 */
|
||||
_pem_b_type?: T;
|
||||
_pem_type?: S;
|
||||
}
|
||||
|
||||
export function encodePEM<T extends WrappedBuffer, S extends ValidPEMType>(data: T, pemType: S): PEMString<T, S>;
|
||||
export function encodePEM(data: Buffer, pemType: ValidPEMType): string;
|
||||
export function encodePEM(data: Buffer, pemType: ValidPEMType): string {
|
||||
const base64text = data.toString('base64');
|
||||
const splitText = base64text.match(/.{1,64}/g)?.join('\n') ?? '';
|
||||
|
||||
return `-----BEGIN ${pemType}-----
|
||||
${splitText}
|
||||
-----END ${pemType}-----
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type InferPEMType<T extends PEMString<any, any>> = Exclude<undefined, T['_pem_type']>;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type InferPEMBuffer<T extends PEMString<any, any>> = Exclude<undefined, T['_pem_b_type']>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function decodePEM<T extends PEMString<any, any>>(pem: T, pemType: InferPEMType<T>): InferPEMBuffer<T>[];
|
||||
export function decodePEM(pem: string, pemType?: ValidPEMType): Buffer[];
|
||||
export function decodePEM(pem: string, pemType?: ValidPEMType): Buffer[] {
|
||||
const tag = pemType ?? "[A-Z0-9 ]+";
|
||||
const pattern = new RegExp(`-{5}BEGIN ${tag}-{5}([a-zA-Z0-9=+\\/\\n\\r]+)-{5}END ${tag}-{5}`, "g");
|
||||
|
||||
const res: Buffer[] = [];
|
||||
let matches: RegExpExecArray | null = null;
|
||||
// eslint-disable-next-line no-cond-assign
|
||||
while (matches = pattern.exec(pem)) {
|
||||
const base64 = matches[1]
|
||||
.replace(/\r/g, "")
|
||||
.replace(/\n/g, "");
|
||||
res.push(Buffer.from(base64, 'base64'));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function decodeSinglePEM<T extends PEMString<any, any>>(pem: T, pemType: InferPEMType<T>): InferPEMBuffer<T>;
|
||||
export function decodeSinglePEM(pem: string, pemType?: ValidPEMType): Buffer;
|
||||
export function decodeSinglePEM(pem: string, pemType?: ValidPEMType): Buffer {
|
||||
const res = decodePEM(pem, pemType);
|
||||
if (res.length != 1) {
|
||||
throw new Error("invalid pem");
|
||||
}
|
||||
return res[0];
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"verbatimModuleSyntax": false,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "../util"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "@sammo/game_logic",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc --build"
|
||||
},
|
||||
"author": "",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sammo/api_def": "workspace:^",
|
||||
"@sammo/server_util": "workspace:^",
|
||||
"@sammo/util": "workspace:^",
|
||||
"@strpc/express": "workspace:^",
|
||||
"dotenv": "^16.3.1",
|
||||
"mongoose": "^7.4.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.6.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
interface Stats { min: number, max: number, total: number };
|
||||
|
||||
export function abilityRand(stats: Stats): [number, number, number] {
|
||||
let leadership = Math.random() * 65 + 10;
|
||||
let strength = Math.random() * 65 + 10;
|
||||
let intel = Math.random() * 65 + 10;
|
||||
const rate = leadership + strength + intel;
|
||||
|
||||
leadership = Math.floor((leadership / rate) * stats.total);
|
||||
strength = Math.floor((strength / rate) * stats.total);
|
||||
intel = Math.floor((intel / rate) * stats.total);
|
||||
|
||||
while (leadership + strength + intel < stats.total) {
|
||||
leadership += 1;
|
||||
}
|
||||
|
||||
if (
|
||||
leadership > stats.max ||
|
||||
strength > stats.max ||
|
||||
intel > stats.max ||
|
||||
leadership < stats.min ||
|
||||
strength < stats.min ||
|
||||
intel < stats.min
|
||||
) {
|
||||
return abilityRand(stats);
|
||||
}
|
||||
|
||||
return [leadership, strength, intel];
|
||||
}
|
||||
|
||||
export function abilityLeadpow(stats: Stats): [number, number, number] {
|
||||
let leadership = Math.random() * 6;
|
||||
let strength = Math.random() * 6;
|
||||
let intel = Math.random() * 1;
|
||||
const rate = leadership + strength + intel;
|
||||
|
||||
leadership = Math.floor((leadership / rate) * stats.total);
|
||||
strength = Math.floor((strength / rate) * stats.total);
|
||||
intel = Math.floor((intel / rate) * stats.total);
|
||||
|
||||
while (leadership + strength + intel < stats.total) {
|
||||
strength += 1;
|
||||
}
|
||||
|
||||
if (intel < stats.min) {
|
||||
leadership -= stats.min - intel;
|
||||
intel = stats.min;
|
||||
}
|
||||
|
||||
if (leadership > stats.max) {
|
||||
strength += leadership - stats.max;
|
||||
leadership = stats.max;
|
||||
}
|
||||
|
||||
if (strength > stats.max) {
|
||||
leadership += strength - stats.max;
|
||||
strength = stats.max;
|
||||
}
|
||||
|
||||
if (leadership > stats.max) {
|
||||
intel += leadership - stats.max;
|
||||
leadership = stats.max;
|
||||
}
|
||||
|
||||
return [leadership, strength, intel];
|
||||
}
|
||||
|
||||
export function abilityLeadint(stats: Stats): [number, number, number] {
|
||||
let leadership = Math.random() * 6;
|
||||
let strength = Math.random() * 1;
|
||||
let intel = Math.random() * 6;
|
||||
const rate = leadership + strength + intel;
|
||||
|
||||
leadership = Math.floor((leadership / rate) * stats.total);
|
||||
strength = Math.floor((strength / rate) * stats.total);
|
||||
intel = Math.floor((intel / rate) * stats.total);
|
||||
|
||||
while (leadership + strength + intel < stats.total) {
|
||||
intel += 1;
|
||||
}
|
||||
|
||||
if (strength < stats.min) {
|
||||
leadership -= stats.min - strength;
|
||||
strength = stats.min;
|
||||
}
|
||||
|
||||
if (leadership > stats.max) {
|
||||
intel += leadership - stats.max;
|
||||
leadership = stats.max;
|
||||
}
|
||||
|
||||
if (intel > stats.max) {
|
||||
leadership += intel - stats.max;
|
||||
intel = stats.max;
|
||||
}
|
||||
|
||||
if (leadership > stats.max) {
|
||||
strength += leadership - stats.max;
|
||||
leadership = stats.max;
|
||||
}
|
||||
|
||||
return [leadership, strength, intel];
|
||||
}
|
||||
|
||||
export function abilityPowint(stats: Stats): [number, number, number] {
|
||||
let leadership = Math.random() * 1;
|
||||
let strength = Math.random() * 6;
|
||||
let intel = Math.random() * 6;
|
||||
const rate = leadership + strength + intel;
|
||||
|
||||
leadership = Math.floor((leadership / rate) * stats.total);
|
||||
strength = Math.floor((strength / rate) * stats.total);
|
||||
intel = Math.floor((intel / rate) * stats.total);
|
||||
|
||||
while (leadership + strength + intel < stats.total) {
|
||||
intel += 1;
|
||||
}
|
||||
|
||||
if (leadership < stats.min) {
|
||||
strength -= stats.min - leadership;
|
||||
leadership = stats.min;
|
||||
}
|
||||
|
||||
if (strength > stats.max) {
|
||||
intel += strength - stats.max;
|
||||
strength = stats.max;
|
||||
}
|
||||
|
||||
if (intel > stats.max) {
|
||||
strength += intel - stats.max;
|
||||
intel = stats.max;
|
||||
}
|
||||
|
||||
if (strength > stats.max) {
|
||||
leadership += strength - stats.max;
|
||||
strength = stats.max;
|
||||
}
|
||||
|
||||
return [leadership, strength, intel];
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./generalStats.js";
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
},
|
||||
"references": [
|
||||
{
|
||||
"path": "../../@strpc/express"
|
||||
},
|
||||
{
|
||||
"path": "../util"
|
||||
},
|
||||
{
|
||||
"path": "../crypto"
|
||||
},
|
||||
{
|
||||
"path": "../server_util"
|
||||
},
|
||||
]
|
||||
}
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "@sammo/gateway_client",
|
||||
"version": "1.0.1",
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "run-p type-check build-only",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false",
|
||||
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
|
||||
"format": "prettier --write src/"
|
||||
},
|
||||
"author": "",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@sammo/util": "workspace:^",
|
||||
"@strpc/def": "workspace:^",
|
||||
"bootstrap": "^5.3.1",
|
||||
"bootstrap-vue-next": "^0.9.26",
|
||||
"bson": "^5.4.0",
|
||||
"date-fns": "^2.30.0",
|
||||
"ky": "^1.0.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"pinia": "^2.1.4",
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "^4.2.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tsconfig/node20": "^20.1.2",
|
||||
"@types/lodash-es": "^4.17.9",
|
||||
"@types/node": "^20.6.3",
|
||||
"@vue/eslint-config-prettier": "^8.0.0",
|
||||
"@vue/eslint-config-typescript": "^11.0.3",
|
||||
"@vue/tsconfig": "^0.4.0",
|
||||
"eslint": "^8.50.0",
|
||||
"eslint-plugin-vue": "^9.15.1",
|
||||
"prettier": "^3.0.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"tslib": "^2.6.2",
|
||||
"vite": "^4.4.6",
|
||||
"vue-tsc": "^1.8.6",
|
||||
"zod": "^3.22.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"zod": "^3.22.2"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
@@ -0,0 +1,85 @@
|
||||
<script setup lang="ts">
|
||||
import { RouterLink, RouterView } from 'vue-router'
|
||||
import HelloWorld from './components/HelloWorld.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header>
|
||||
<img alt="Vue logo" class="logo" src="@/assets/logo.svg" width="125" height="125" />
|
||||
|
||||
<div class="wrapper">
|
||||
<HelloWorld msg="You did it!" />
|
||||
|
||||
<nav>
|
||||
<RouterLink to="/">Home</RouterLink>
|
||||
<RouterLink to="/about">About</RouterLink>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<RouterView />
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
header {
|
||||
line-height: 1.5;
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: block;
|
||||
margin: 0 auto 2rem;
|
||||
}
|
||||
|
||||
nav {
|
||||
width: 100%;
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
nav a.router-link-exact-active {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
nav a.router-link-exact-active:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
nav a {
|
||||
display: inline-block;
|
||||
padding: 0 1rem;
|
||||
border-left: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
nav a:first-of-type {
|
||||
border: 0;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
header {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
padding-right: calc(var(--section-gap) / 2);
|
||||
}
|
||||
|
||||
.logo {
|
||||
margin: 0 2rem 0 0;
|
||||
}
|
||||
|
||||
header .wrapper {
|
||||
display: flex;
|
||||
place-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
nav {
|
||||
text-align: left;
|
||||
margin-left: -1rem;
|
||||
font-size: 1rem;
|
||||
|
||||
padding: 1rem 0;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,86 @@
|
||||
/* color palette from <https://github.com/vuejs/theme> */
|
||||
:root {
|
||||
--vt-c-white: #ffffff;
|
||||
--vt-c-white-soft: #f8f8f8;
|
||||
--vt-c-white-mute: #f2f2f2;
|
||||
|
||||
--vt-c-black: #181818;
|
||||
--vt-c-black-soft: #222222;
|
||||
--vt-c-black-mute: #282828;
|
||||
|
||||
--vt-c-indigo: #2c3e50;
|
||||
|
||||
--vt-c-divider-light-1: rgba(60, 60, 60, 0.29);
|
||||
--vt-c-divider-light-2: rgba(60, 60, 60, 0.12);
|
||||
--vt-c-divider-dark-1: rgba(84, 84, 84, 0.65);
|
||||
--vt-c-divider-dark-2: rgba(84, 84, 84, 0.48);
|
||||
|
||||
--vt-c-text-light-1: var(--vt-c-indigo);
|
||||
--vt-c-text-light-2: rgba(60, 60, 60, 0.66);
|
||||
--vt-c-text-dark-1: var(--vt-c-white);
|
||||
--vt-c-text-dark-2: rgba(235, 235, 235, 0.64);
|
||||
}
|
||||
|
||||
/* semantic color variables for this project */
|
||||
:root {
|
||||
--color-background: var(--vt-c-white);
|
||||
--color-background-soft: var(--vt-c-white-soft);
|
||||
--color-background-mute: var(--vt-c-white-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-light-2);
|
||||
--color-border-hover: var(--vt-c-divider-light-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-light-1);
|
||||
--color-text: var(--vt-c-text-light-1);
|
||||
|
||||
--section-gap: 160px;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--color-background: var(--vt-c-black);
|
||||
--color-background-soft: var(--vt-c-black-soft);
|
||||
--color-background-mute: var(--vt-c-black-mute);
|
||||
|
||||
--color-border: var(--vt-c-divider-dark-2);
|
||||
--color-border-hover: var(--vt-c-divider-dark-1);
|
||||
|
||||
--color-heading: var(--vt-c-text-dark-1);
|
||||
--color-text: var(--vt-c-text-dark-2);
|
||||
}
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
body {
|
||||
min-height: 100vh;
|
||||
color: var(--color-text);
|
||||
background: var(--color-background);
|
||||
transition:
|
||||
color 0.5s,
|
||||
background-color 0.5s;
|
||||
line-height: 1.6;
|
||||
font-family:
|
||||
Inter,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
Roboto,
|
||||
Oxygen,
|
||||
Ubuntu,
|
||||
Cantarell,
|
||||
'Fira Sans',
|
||||
'Droid Sans',
|
||||
'Helvetica Neue',
|
||||
sans-serif;
|
||||
font-size: 15px;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>
|
||||
|
After Width: | Height: | Size: 276 B |
@@ -0,0 +1,35 @@
|
||||
@import './base.css';
|
||||
|
||||
#app {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
a,
|
||||
.green {
|
||||
text-decoration: none;
|
||||
color: hsla(160, 100%, 37%, 1);
|
||||
transition: 0.4s;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
a:hover {
|
||||
background-color: hsla(160, 100%, 37%, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
body {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
#app {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
padding: 0 2rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
<script setup lang="ts">
|
||||
defineProps<{
|
||||
msg: string
|
||||
}>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="greetings">
|
||||
<h1 class="green">{{ msg }}</h1>
|
||||
<h3>
|
||||
You’ve successfully created a project with
|
||||
<a href="https://vitejs.dev/" target="_blank" rel="noopener">Vite</a> +
|
||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">Vue 3</a>. What's next?
|
||||
</h3>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
h1 {
|
||||
font-weight: 500;
|
||||
font-size: 2.6rem;
|
||||
position: relative;
|
||||
top: -10px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.greetings h1,
|
||||
.greetings h3 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.greetings h1,
|
||||
.greetings h3 {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,86 @@
|
||||
<script setup lang="ts">
|
||||
import WelcomeItem from './WelcomeItem.vue'
|
||||
import DocumentationIcon from './icons/IconDocumentation.vue'
|
||||
import ToolingIcon from './icons/IconTooling.vue'
|
||||
import EcosystemIcon from './icons/IconEcosystem.vue'
|
||||
import CommunityIcon from './icons/IconCommunity.vue'
|
||||
import SupportIcon from './icons/IconSupport.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<DocumentationIcon />
|
||||
</template>
|
||||
<template #heading>Documentation</template>
|
||||
|
||||
Vue’s
|
||||
<a href="https://vuejs.org/" target="_blank" rel="noopener">official documentation</a>
|
||||
provides you with all information you need to get started.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<ToolingIcon />
|
||||
</template>
|
||||
<template #heading>Tooling</template>
|
||||
|
||||
This project is served and bundled with
|
||||
<a href="https://vitejs.dev/guide/features.html" target="_blank" rel="noopener">Vite</a>. The
|
||||
recommended IDE setup is
|
||||
<a href="https://code.visualstudio.com/" target="_blank" rel="noopener">VSCode</a> +
|
||||
<a href="https://github.com/johnsoncodehk/volar" target="_blank" rel="noopener">Volar</a>. If
|
||||
you need to test your components and web pages, check out
|
||||
<a href="https://www.cypress.io/" target="_blank" rel="noopener">Cypress</a> and
|
||||
<a href="https://on.cypress.io/component" target="_blank">Cypress Component Testing</a>.
|
||||
|
||||
<br />
|
||||
|
||||
More instructions are available in <code>README.md</code>.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<EcosystemIcon />
|
||||
</template>
|
||||
<template #heading>Ecosystem</template>
|
||||
|
||||
Get official tools and libraries for your project:
|
||||
<a href="https://pinia.vuejs.org/" target="_blank" rel="noopener">Pinia</a>,
|
||||
<a href="https://router.vuejs.org/" target="_blank" rel="noopener">Vue Router</a>,
|
||||
<a href="https://test-utils.vuejs.org/" target="_blank" rel="noopener">Vue Test Utils</a>, and
|
||||
<a href="https://github.com/vuejs/devtools" target="_blank" rel="noopener">Vue Dev Tools</a>. If
|
||||
you need more resources, we suggest paying
|
||||
<a href="https://github.com/vuejs/awesome-vue" target="_blank" rel="noopener">Awesome Vue</a>
|
||||
a visit.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<CommunityIcon />
|
||||
</template>
|
||||
<template #heading>Community</template>
|
||||
|
||||
Got stuck? Ask your question on
|
||||
<a href="https://chat.vuejs.org" target="_blank" rel="noopener">Vue Land</a>, our official
|
||||
Discord server, or
|
||||
<a href="https://stackoverflow.com/questions/tagged/vue.js" target="_blank" rel="noopener"
|
||||
>StackOverflow</a
|
||||
>. You should also subscribe to
|
||||
<a href="https://news.vuejs.org" target="_blank" rel="noopener">our mailing list</a> and follow
|
||||
the official
|
||||
<a href="https://twitter.com/vuejs" target="_blank" rel="noopener">@vuejs</a>
|
||||
twitter account for latest news in the Vue world.
|
||||
</WelcomeItem>
|
||||
|
||||
<WelcomeItem>
|
||||
<template #icon>
|
||||
<SupportIcon />
|
||||
</template>
|
||||
<template #heading>Support Vue</template>
|
||||
|
||||
As an independent project, Vue relies on community backing for its sustainability. You can help
|
||||
us by
|
||||
<a href="https://vuejs.org/sponsor/" target="_blank" rel="noopener">becoming a sponsor</a>.
|
||||
</WelcomeItem>
|
||||
</template>
|
||||
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<div class="item">
|
||||
<i>
|
||||
<slot name="icon"></slot>
|
||||
</i>
|
||||
<div class="details">
|
||||
<h3>
|
||||
<slot name="heading"></slot>
|
||||
</h3>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.item {
|
||||
margin-top: 2rem;
|
||||
display: flex;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.details {
|
||||
flex: 1;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
i {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
place-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.4rem;
|
||||
color: var(--color-heading);
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.item {
|
||||
margin-top: 0;
|
||||
padding: 0.4rem 0 1rem calc(var(--section-gap) / 2);
|
||||
}
|
||||
|
||||
i {
|
||||
top: calc(50% - 25px);
|
||||
left: -26px;
|
||||
position: absolute;
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-background);
|
||||
border-radius: 8px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.item:before {
|
||||
content: ' ';
|
||||
border-left: 1px solid var(--color-border);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: calc(50% + 25px);
|
||||
height: calc(50% - 25px);
|
||||
}
|
||||
|
||||
.item:after {
|
||||
content: ' ';
|
||||
border-left: 1px solid var(--color-border);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: calc(50% + 25px);
|
||||
height: calc(50% - 25px);
|
||||
}
|
||||
|
||||
.item:first-of-type:before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.item:last-of-type:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
||||
<path
|
||||
d="M15 4a1 1 0 1 0 0 2V4zm0 11v-1a1 1 0 0 0-1 1h1zm0 4l-.707.707A1 1 0 0 0 16 19h-1zm-4-4l.707-.707A1 1 0 0 0 11 14v1zm-4.707-1.293a1 1 0 0 0-1.414 1.414l1.414-1.414zm-.707.707l-.707-.707.707.707zM9 11v-1a1 1 0 0 0-.707.293L9 11zm-4 0h1a1 1 0 0 0-1-1v1zm0 4H4a1 1 0 0 0 1.707.707L5 15zm10-9h2V4h-2v2zm2 0a1 1 0 0 1 1 1h2a3 3 0 0 0-3-3v2zm1 1v6h2V7h-2zm0 6a1 1 0 0 1-1 1v2a3 3 0 0 0 3-3h-2zm-1 1h-2v2h2v-2zm-3 1v4h2v-4h-2zm1.707 3.293l-4-4-1.414 1.414 4 4 1.414-1.414zM11 14H7v2h4v-2zm-4 0c-.276 0-.525-.111-.707-.293l-1.414 1.414C5.42 15.663 6.172 16 7 16v-2zm-.707 1.121l3.414-3.414-1.414-1.414-3.414 3.414 1.414 1.414zM9 12h4v-2H9v2zm4 0a3 3 0 0 0 3-3h-2a1 1 0 0 1-1 1v2zm3-3V3h-2v6h2zm0-6a3 3 0 0 0-3-3v2a1 1 0 0 1 1 1h2zm-3-3H3v2h10V0zM3 0a3 3 0 0 0-3 3h2a1 1 0 0 1 1-1V0zM0 3v6h2V3H0zm0 6a3 3 0 0 0 3 3v-2a1 1 0 0 1-1-1H0zm3 3h2v-2H3v2zm1-1v4h2v-4H4zm1.707 4.707l.586-.586-1.414-1.414-.586.586 1.414 1.414z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="17" fill="currentColor">
|
||||
<path
|
||||
d="M11 2.253a1 1 0 1 0-2 0h2zm-2 13a1 1 0 1 0 2 0H9zm.447-12.167a1 1 0 1 0 1.107-1.666L9.447 3.086zM1 2.253L.447 1.42A1 1 0 0 0 0 2.253h1zm0 13H0a1 1 0 0 0 1.553.833L1 15.253zm8.447.833a1 1 0 1 0 1.107-1.666l-1.107 1.666zm0-14.666a1 1 0 1 0 1.107 1.666L9.447 1.42zM19 2.253h1a1 1 0 0 0-.447-.833L19 2.253zm0 13l-.553.833A1 1 0 0 0 20 15.253h-1zm-9.553-.833a1 1 0 1 0 1.107 1.666L9.447 14.42zM9 2.253v13h2v-13H9zm1.553-.833C9.203.523 7.42 0 5.5 0v2c1.572 0 2.961.431 3.947 1.086l1.107-1.666zM5.5 0C3.58 0 1.797.523.447 1.42l1.107 1.666C2.539 2.431 3.928 2 5.5 2V0zM0 2.253v13h2v-13H0zm1.553 13.833C2.539 15.431 3.928 15 5.5 15v-2c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM5.5 15c1.572 0 2.961.431 3.947 1.086l1.107-1.666C9.203 13.523 7.42 13 5.5 13v2zm5.053-11.914C11.539 2.431 12.928 2 14.5 2V0c-1.92 0-3.703.523-5.053 1.42l1.107 1.666zM14.5 2c1.573 0 2.961.431 3.947 1.086l1.107-1.666C18.203.523 16.421 0 14.5 0v2zm3.5.253v13h2v-13h-2zm1.553 12.167C18.203 13.523 16.421 13 14.5 13v2c1.573 0 2.961.431 3.947 1.086l1.107-1.666zM14.5 13c-1.92 0-3.703.523-5.053 1.42l1.107 1.666C11.539 15.431 12.928 15 14.5 15v-2z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" fill="currentColor">
|
||||
<path
|
||||
d="M10 3.22l-.61-.6a5.5 5.5 0 0 0-7.666.105 5.5 5.5 0 0 0-.114 7.665L10 18.78l8.39-8.4a5.5 5.5 0 0 0-.114-7.665 5.5 5.5 0 0 0-7.666-.105l-.61.61z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
@@ -0,0 +1,19 @@
|
||||
<!-- This icon is from <https://github.com/Templarian/MaterialDesign>, distributed under Apache 2.0 (https://www.apache.org/licenses/LICENSE-2.0) license-->
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
aria-hidden="true"
|
||||
role="img"
|
||||
class="iconify iconify--mdi"
|
||||
width="24"
|
||||
height="24"
|
||||
preserveAspectRatio="xMidYMid meet"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M20 18v-4h-3v1h-2v-1H9v1H7v-1H4v4h16M6.33 8l-1.74 4H7v-1h2v1h6v-1h2v1h2.41l-1.74-4H6.33M9 5v1h6V5H9m12.84 7.61c.1.22.16.48.16.8V18c0 .53-.21 1-.6 1.41c-.4.4-.85.59-1.4.59H4c-.55 0-1-.19-1.4-.59C2.21 19 2 18.53 2 18v-4.59c0-.32.06-.58.16-.8L4.5 7.22C4.84 6.41 5.45 6 6.33 6H7V5c0-.55.18-1 .57-1.41C7.96 3.2 8.44 3 9 3h6c.56 0 1.04.2 1.43.59c.39.41.57.86.57 1.41v1h.67c.88 0 1.49.41 1.83 1.22l2.34 5.39z"
|
||||
fill="currentColor"
|
||||
></path>
|
||||
</svg>
|
||||
</template>
|
||||
@@ -0,0 +1,14 @@
|
||||
import './assets/main.css'
|
||||
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
|
||||
import App from './App.vue'
|
||||
import router from './router/index.js'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
|
||||
app.mount('#app')
|
||||
@@ -0,0 +1,23 @@
|
||||
import { createRouter, createWebHistory } from 'vue-router'
|
||||
import HomeView from '../views/HomeView.vue'
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes: [
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: HomeView
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
name: 'about',
|
||||
// route level code-splitting
|
||||
// this generates a separate chunk (About.[hash].js) for this route
|
||||
// which is lazy-loaded when the route is visited.
|
||||
component: () => import('../views/AboutView.vue')
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -0,0 +1,12 @@
|
||||
import { ref, computed } from 'vue'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export const useCounterStore = defineStore('counter', () => {
|
||||
const count = ref(0)
|
||||
const doubleCount = computed(() => count.value * 2)
|
||||
function increment() {
|
||||
count.value++
|
||||
}
|
||||
|
||||
return { count, doubleCount, increment }
|
||||
})
|
||||
@@ -0,0 +1,15 @@
|
||||
<template>
|
||||
<div class="about">
|
||||
<h1>This is an about page</h1>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@media (min-width: 1024px) {
|
||||
.about {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import TheWelcome from '../components/TheWelcome.vue'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<TheWelcome />
|
||||
</main>
|
||||
</template>
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"include": ["env.d.ts", "src/**/*", "src/**/*.vue"],
|
||||
"exclude": ["src/**/__tests__/*"],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
},
|
||||
"exclude": [
|
||||
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
|
||||
"path": "../util"
|
||||
},
|
||||
{
|
||||
"path": "../secure_token"
|
||||
},
|
||||
{
|
||||
"path": "../../@strpc/def"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.app.json"
|
||||
},
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": "@tsconfig/node20/tsconfig.json",
|
||||
"include": [
|
||||
"vite.config.*",
|
||||
"vitest.config.*",
|
||||
"cypress.config.*",
|
||||
"nightwatch.conf.*",
|
||||
"playwright.config.*"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"types": ["node"],
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { fileURLToPath, URL } from 'node:url'
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
vue(),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url)),
|
||||
'@util': fileURLToPath(new URL('./server/util', import.meta.url)),
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"name": "@sammo/gateway_server",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc --build"
|
||||
},
|
||||
"exports": {
|
||||
".": "./dist/index.js",
|
||||
"./exports": "./dist/exports.js"
|
||||
},
|
||||
"author": "",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@sammo/api_def": "workspace:^",
|
||||
"@sammo/crypto": "workspace:^",
|
||||
"@sammo/server_util": "workspace:^",
|
||||
"@sammo/util": "workspace:^",
|
||||
"@strpc/express": "workspace:^",
|
||||
"date-fns": "^2.30.0",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"express-session": "^1.17.3",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mongoose": "^7.4.3",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"tslib": "^2.6.2",
|
||||
"zod": "^3.22.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/express-session": "^1.17.7",
|
||||
"@types/node": "^20.6.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { StateIncrementer } from '@sammo/server_util';
|
||||
import SchemaSequence from './schema/SchemaSequence.js';
|
||||
import { InvalidArgument } from '@sammo/util';
|
||||
|
||||
export function MongoSequenceFactory(collectionName: string): StateIncrementer{
|
||||
|
||||
return async (increase: number) => {
|
||||
if(increase <= 0){
|
||||
throw new InvalidArgument('increase must be > 0');
|
||||
}
|
||||
increase = Math.ceil(increase);
|
||||
|
||||
const result = await SchemaSequence.findOneAndUpdate({
|
||||
collectionName,
|
||||
}, {
|
||||
$inc: {
|
||||
nextSeq: increase,
|
||||
},
|
||||
}, {
|
||||
upsert: true,
|
||||
new: true,
|
||||
});
|
||||
return result.nextSeq;
|
||||
}
|
||||
}
|
||||
@@ -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,62 @@
|
||||
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 Map(),
|
||||
allowGatewayAction: new Set(),
|
||||
loginDate: new Date(),
|
||||
}
|
||||
|
||||
ctx.session.setItem(loginCtxSessionKey, loginCtx);
|
||||
|
||||
|
||||
return {
|
||||
result: true,
|
||||
nextToken,
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
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.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): RType => {
|
||||
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(),
|
||||
allowGatewayAction: new Set(),
|
||||
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,11 @@
|
||||
import type { structure } from "@sammo/api_def/gateway";
|
||||
import type { APINamespaceType } from "@strpc/express";
|
||||
import { LoginByID } from "./LoginByID.js";
|
||||
import { LoginByToken } from "./LoginByToken.js";
|
||||
import { ReqNonce } from "./ReqNonce.js";
|
||||
|
||||
export const Login = {
|
||||
LoginByID,
|
||||
LoginByToken,
|
||||
ReqNonce,
|
||||
} satisfies APINamespaceType<typeof structure.Login>;
|
||||
@@ -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,22 @@
|
||||
import './dotenv.js';
|
||||
import { connect, Mongoose } from "mongoose";
|
||||
import { unwrap } from '@sammo/util';
|
||||
|
||||
const dbConfig = {
|
||||
host: unwrap(process.env.GATEWAY_DB_HOST),
|
||||
port: Number(unwrap(process.env.GATEWAY_DB_PORT)),
|
||||
user: unwrap(process.env.GATEWAY_DB_USER),
|
||||
password: unwrap(process.env.GATEWAY_DB_PASSWORD),
|
||||
database: unwrap(process.env.GATEWAY_DB_DATABASE),
|
||||
}
|
||||
|
||||
const db: Promise<Mongoose> = (async () => {
|
||||
return await connect(`mongodb://${dbConfig.host}:${dbConfig.port}/${dbConfig.database}`, {
|
||||
auth:{
|
||||
username: dbConfig.user,
|
||||
password: dbConfig.password,
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
export default db;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user