@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 ADDED
@@ -0,0 +1,3 @@
1
+ # @tomo-inc/oidc-auth
2
+
3
+ ## 0.0.1
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
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "baseUrl": "./src"
5
+ },
6
+ "include": ["src"]
7
+ }
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
+ });