@tomo-inc/oidc-auth 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +3 -0
- package/README.md +39 -0
- package/package.json +35 -0
- package/project.json +59 -0
- package/src/const.ts +12 -0
- package/src/index.ts +156 -0
- package/src/utils.ts +35 -0
- package/tsconfig.json +7 -0
- package/tsup.config.ts +18 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# @tomo-inc/oidc-auth
|
|
2
|
+
|
|
3
|
+
## quick start
|
|
4
|
+
|
|
5
|
+
### 1. set config, get api
|
|
6
|
+
|
|
7
|
+
link1: [how to config x]()
|
|
8
|
+
link2: [hot to config google]()
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
const config = {
|
|
12
|
+
xClientId,
|
|
13
|
+
googleClientId,
|
|
14
|
+
tomoStage: "dev" | "pre" | "prod",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const { loginByGoogle, loginByX, loginByEmail } = OidcAuth(config);
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### 2. get oidcToken(google/x)
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
const oidcToken = await loginByGoogle();
|
|
24
|
+
const oidcToken = await loginByX();
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### 3. get oidcToken(email)
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
const oidcTokenPart1 = await loginByEmail("test@test.com");
|
|
31
|
+
const oidcTokenPart2 = "${oidcTokenPart2}"; //input by user, from email conent
|
|
32
|
+
const oidcToken = `${oidcTokenPart1}${oidcTokenPart2}`;
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## todo
|
|
36
|
+
|
|
37
|
+
1. more login method
|
|
38
|
+
2. relay remove
|
|
39
|
+
3. webpage performance
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tomo-inc/oidc-auth",
|
|
3
|
+
"version": "0.0.3",
|
|
4
|
+
"author": "tomo.inc",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"private": false,
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.cjs",
|
|
9
|
+
"module": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"require": "./dist/index.cjs"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@cubist-labs/cubesigner-sdk": "0.4.98-0",
|
|
20
|
+
"@tomo-inc/cubist-sig-sdk": "1.1.0",
|
|
21
|
+
"@tomo-inc/wallet-utils": "0.0.3"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/crypto-js": "^4.2.2",
|
|
25
|
+
"@types/node": "^20.0.0",
|
|
26
|
+
"@types/supertest": "^2.0.12",
|
|
27
|
+
"supertest": "^6.3.0",
|
|
28
|
+
"tsup": "^8.0.0",
|
|
29
|
+
"tsx": "^4.19.2",
|
|
30
|
+
"typescript": "^5.0.0",
|
|
31
|
+
"@vitest/browser": "^3.2.4",
|
|
32
|
+
"playwright": "^1.44.1",
|
|
33
|
+
"vitest": "^3.2.4"
|
|
34
|
+
}
|
|
35
|
+
}
|
package/project.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "oidc-auth",
|
|
3
|
+
"sourceRoot": "packages/oidc-auth/src",
|
|
4
|
+
"projectType": "library",
|
|
5
|
+
"targets": {
|
|
6
|
+
"build": {
|
|
7
|
+
"executor": "nx:run-commands",
|
|
8
|
+
"outputs": ["{projectRoot}/dist"],
|
|
9
|
+
"options": {
|
|
10
|
+
"command": "tsup src/index.ts --format esm,cjs --dts --treeshake",
|
|
11
|
+
"cwd": "packages/oidc-auth"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"dev": {
|
|
15
|
+
"executor": "nx:run-commands",
|
|
16
|
+
"options": {
|
|
17
|
+
"command": "tsup src/index.ts --format esm,cjs --watch --dts",
|
|
18
|
+
"cwd": "packages/oidc-auth"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"lint": {
|
|
22
|
+
"executor": "nx:run-commands",
|
|
23
|
+
"options": {
|
|
24
|
+
"command": "eslint src/**/*.ts",
|
|
25
|
+
"cwd": "packages/oidc-auth"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"lint:fix": {
|
|
29
|
+
"executor": "nx:run-commands",
|
|
30
|
+
"options": {
|
|
31
|
+
"command": "eslint src/**/*.ts --fix",
|
|
32
|
+
"cwd": "packages/oidc-auth"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"format": {
|
|
36
|
+
"executor": "nx:run-commands",
|
|
37
|
+
"options": {
|
|
38
|
+
"command": "prettier --write \"src/**/*.{ts,tsx}\"",
|
|
39
|
+
"cwd": "packages/oidc-auth"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"test": {
|
|
43
|
+
"executor": "nx:run-commands",
|
|
44
|
+
"outputs": ["{projectRoot}/coverage"],
|
|
45
|
+
"options": {
|
|
46
|
+
"command": "vitest run",
|
|
47
|
+
"cwd": "packages/oidc-auth"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"test:watch": {
|
|
51
|
+
"executor": "nx:run-commands",
|
|
52
|
+
"options": {
|
|
53
|
+
"command": "vitest",
|
|
54
|
+
"cwd": "packages/oidc-auth"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"tags": ["npm:private", "scope:oidc-auth", "type:library"]
|
|
59
|
+
}
|
package/src/const.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const RelayMessageTypes = {
|
|
2
|
+
googleAuth: "tomo-google-authorized",
|
|
3
|
+
xAuth: "tomo-twitter-authorized",
|
|
4
|
+
kakaoAuth: "tomo-kakao-authorized",
|
|
5
|
+
telegramAuth: "tomo-telegram-authorized",
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export const EmailCodeLifeTime = 60; // 60 seconds
|
|
9
|
+
|
|
10
|
+
export type LoginType = "google" | "x" | "email";
|
|
11
|
+
|
|
12
|
+
export type EmailLoginResult = { partialOidcToken: string; lifeTime: number };
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { envs } from "@cubist-labs/cubesigner-sdk";
|
|
2
|
+
import { emailLogin } from "@tomo-inc/cubist-sig-sdk";
|
|
3
|
+
import { CubeOrgIds, CubeStage, CubeStages, isEmail, RelayOrigins, TomoStage } from "@tomo-inc/wallet-utils";
|
|
4
|
+
|
|
5
|
+
import { EmailCodeLifeTime, EmailLoginResult, LoginType, RelayMessageTypes } from "./const";
|
|
6
|
+
import { openWindow } from "./utils";
|
|
7
|
+
|
|
8
|
+
export type { EmailLoginResult, LoginType };
|
|
9
|
+
|
|
10
|
+
interface Config {
|
|
11
|
+
tomoStage: TomoStage;
|
|
12
|
+
xClientId: string;
|
|
13
|
+
googleClientId: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const OidcAuth = ({ tomoStage, xClientId, googleClientId }: Config) => {
|
|
17
|
+
const relayOrigin = RelayOrigins[tomoStage];
|
|
18
|
+
if (!relayOrigin) {
|
|
19
|
+
throw new Error("Invalid tomo stage");
|
|
20
|
+
}
|
|
21
|
+
const xRedirectUri = `${relayOrigin}/x/loader`;
|
|
22
|
+
const googleRedirectUri = `${relayOrigin}/google`;
|
|
23
|
+
|
|
24
|
+
const cubeStage = CubeStages[tomoStage] as CubeStage;
|
|
25
|
+
|
|
26
|
+
const cubeOrgId = CubeOrgIds[tomoStage];
|
|
27
|
+
if (!cubeOrgId) {
|
|
28
|
+
throw new Error("Invalid cube stage");
|
|
29
|
+
}
|
|
30
|
+
const cubeEnv = envs[cubeStage] || envs.gamma;
|
|
31
|
+
const googleOauth2Token = `https://accounts.google.com/o/oauth2/v2/auth`;
|
|
32
|
+
|
|
33
|
+
const waitForOidcToken = (popup: Window | null, timeoutMs: number = 120_000): Promise<string> => {
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
if (!popup) {
|
|
36
|
+
reject(new Error("Popup window is null"));
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const timeout = setTimeout(() => {
|
|
41
|
+
popup.close();
|
|
42
|
+
reject(new Error("Login timeout"));
|
|
43
|
+
}, timeoutMs);
|
|
44
|
+
|
|
45
|
+
const messageHandler = async (event: MessageEvent) => {
|
|
46
|
+
if (event.origin !== relayOrigin) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (event.data.action !== "login") {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const { data, type } = event.data;
|
|
54
|
+
let oidcToken = "";
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
if (type === RelayMessageTypes.googleAuth) {
|
|
58
|
+
oidcToken = data.code;
|
|
59
|
+
} else if (type === RelayMessageTypes.xAuth) {
|
|
60
|
+
oidcToken = data?.oidcToken || "";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (oidcToken) {
|
|
64
|
+
clearTimeout(timeout);
|
|
65
|
+
window.removeEventListener("message", messageHandler);
|
|
66
|
+
popup.close();
|
|
67
|
+
resolve(oidcToken);
|
|
68
|
+
}
|
|
69
|
+
} catch (error) {
|
|
70
|
+
clearTimeout(timeout);
|
|
71
|
+
window.removeEventListener("message", messageHandler);
|
|
72
|
+
popup.close();
|
|
73
|
+
reject(error);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
window.addEventListener("message", messageHandler);
|
|
78
|
+
|
|
79
|
+
// Check if popup is closed manually
|
|
80
|
+
const checkClosed = setInterval(() => {
|
|
81
|
+
if (popup.closed) {
|
|
82
|
+
clearTimeout(timeout);
|
|
83
|
+
clearInterval(checkClosed);
|
|
84
|
+
window.removeEventListener("message", messageHandler);
|
|
85
|
+
reject(new Error("Login cancelled by user"));
|
|
86
|
+
}
|
|
87
|
+
}, 400);
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const loginByGoogle = async (): Promise<string> => {
|
|
92
|
+
const origin = window.location.origin;
|
|
93
|
+
const stateParam = JSON.stringify({ origin, action: "login", loadImg: "" });
|
|
94
|
+
|
|
95
|
+
const params = {
|
|
96
|
+
client_id: googleClientId,
|
|
97
|
+
state: encodeURIComponent(stateParam),
|
|
98
|
+
redirect_uri: googleRedirectUri,
|
|
99
|
+
response_type: "id_token",
|
|
100
|
+
scope: "openid",
|
|
101
|
+
access_type: "offline",
|
|
102
|
+
nonce: Date.now().toString(),
|
|
103
|
+
prompt: "select_account",
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const url = `${googleOauth2Token}?${new URLSearchParams(params).toString()}`;
|
|
107
|
+
|
|
108
|
+
const popup = openWindow({
|
|
109
|
+
url,
|
|
110
|
+
name: "Google login",
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (!popup) {
|
|
114
|
+
throw new Error("Failed to open popup");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return await waitForOidcToken(popup);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const loginByX = async (): Promise<string> => {
|
|
121
|
+
//load_img
|
|
122
|
+
const origin = window.location.origin;
|
|
123
|
+
const params = {
|
|
124
|
+
target: origin,
|
|
125
|
+
tomoStage,
|
|
126
|
+
eventId: Date.now().toString(),
|
|
127
|
+
action: "login",
|
|
128
|
+
clientId: xClientId,
|
|
129
|
+
};
|
|
130
|
+
const url = `${xRedirectUri}?${new URLSearchParams(params).toString()}`;
|
|
131
|
+
const popup = openWindow({
|
|
132
|
+
url,
|
|
133
|
+
name: "X login",
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (!popup) {
|
|
137
|
+
throw new Error("Failed to open popup");
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return await waitForOidcToken(popup);
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
const loginByEmail = async (email: string): Promise<EmailLoginResult> => {
|
|
144
|
+
if (!isEmail(email)) {
|
|
145
|
+
throw new Error("Invalid email");
|
|
146
|
+
}
|
|
147
|
+
const partialOidcToken = await emailLogin(email, cubeEnv, cubeOrgId);
|
|
148
|
+
return { partialOidcToken, lifeTime: EmailCodeLifeTime };
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
loginByGoogle,
|
|
153
|
+
loginByX,
|
|
154
|
+
loginByEmail,
|
|
155
|
+
};
|
|
156
|
+
};
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export const isMobile = () => {
|
|
2
|
+
return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
3
|
+
};
|
|
4
|
+
|
|
5
|
+
interface OpenWindow {
|
|
6
|
+
url: string;
|
|
7
|
+
name?: string;
|
|
8
|
+
width?: number;
|
|
9
|
+
height?: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export const openWindow = ({ url, name, width, height }: OpenWindow): Window | null => {
|
|
13
|
+
const top = (window.innerHeight - (height || 400)) / 2 + window.screenY;
|
|
14
|
+
const left = (window.innerWidth - (width || 400)) / 2 + window.screenX;
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const relyWindow = window.open(
|
|
18
|
+
url,
|
|
19
|
+
name,
|
|
20
|
+
`dialog=yes,top=${top}px,left=${left},width=${width !== undefined ? width : 400}px,height=${height !== undefined ? height : 600}px`,
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
// Fallback to iframe modal if:
|
|
24
|
+
// 1. window.open is blocked by browser
|
|
25
|
+
// 2. iOS Safari requires user interaction for window.open
|
|
26
|
+
if (!relyWindow) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return relyWindow;
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error("Failed to open window:", error);
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
};
|
package/tsconfig.json
ADDED
package/tsup.config.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineConfig } from "tsup";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
entry: ["src/index.ts"],
|
|
5
|
+
format: ["esm", "cjs"],
|
|
6
|
+
dts: true,
|
|
7
|
+
treeshake: true,
|
|
8
|
+
clean: true,
|
|
9
|
+
platform: "browser",
|
|
10
|
+
minify: true,
|
|
11
|
+
splitting: false,
|
|
12
|
+
external: ["@cubist-labs/cubesigner-sdk", "@tomo-inc/cubist-sig-sdk"],
|
|
13
|
+
outExtension({ format }) {
|
|
14
|
+
return {
|
|
15
|
+
js: format === "esm" ? ".js" : ".cjs",
|
|
16
|
+
};
|
|
17
|
+
},
|
|
18
|
+
});
|