@tomo-inc/cubist-wallet-sdk 0.0.4
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 +11 -0
- package/README.md +14 -0
- package/RN.md +54 -0
- package/package.json +40 -0
- package/project.json +59 -0
- package/src/__tests__/cube-mfa.test.ts +64 -0
- package/src/api.ts +64 -0
- package/src/const.ts +96 -0
- package/src/cube-account.ts +324 -0
- package/src/cube-connect.ts +22 -0
- package/src/cube-export.ts +125 -0
- package/src/cube-libs.ts +29 -0
- package/src/cube-mfa.ts +494 -0
- package/src/cube-sign.ts +210 -0
- package/src/export-key.ts +150 -0
- package/src/index.ts +15 -0
- package/src/passkey.ts +221 -0
- package/src/signature.ts +23 -0
- package/src/types.ts +269 -0
- package/src/utils.ts +71 -0
- package/tsconfig.json +15 -0
- package/tsup.config.ts +23 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# @tomo-inc/cubist-wallet-sdk
|
|
2
|
+
|
|
3
|
+
## support
|
|
4
|
+
|
|
5
|
+
1. new user register
|
|
6
|
+
2. user login: get tomoToken + accountAddressList
|
|
7
|
+
3. mfa: config + approval
|
|
8
|
+
4. seed phrase export
|
|
9
|
+
|
|
10
|
+
## todo
|
|
11
|
+
|
|
12
|
+
1. passkey + rpId
|
|
13
|
+
2. integrate more oidc provider
|
|
14
|
+
3. session manage + keep login status
|
package/RN.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Use passkeyRelay in RN
|
|
2
|
+
|
|
3
|
+
## passkeyRelay compatible
|
|
4
|
+
|
|
5
|
+
1. waiting for passkeyRelay info
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
const onMessage = (event) => {
|
|
9
|
+
const data = JSON.parse(event.nativeEvent.data);
|
|
10
|
+
|
|
11
|
+
if (data.type === 'OPEN_PASSKEY_RELAY') {
|
|
12
|
+
const { url, name } = data.message;
|
|
13
|
+
openPasskeyModal(url);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
2. open passkeyRelay url in RN
|
|
19
|
+
|
|
20
|
+
> origin not allow \*
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
const openPasskeyModal = (url) => {
|
|
24
|
+
//just for example
|
|
25
|
+
this.setState({
|
|
26
|
+
showPasskeyModal: true,
|
|
27
|
+
passkeyUrl: `${url}?origin=${origin}`
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
3. waiting for passkeyRelay result
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
const onMessage = (event) => {
|
|
36
|
+
const data = JSON.parse(event.nativeEvent.data);
|
|
37
|
+
|
|
38
|
+
if(data.type === "PASSKEY_RELAY_MESSAGE" && event.origin === origin){
|
|
39
|
+
const result = data.message;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## approvalFido
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
const receipt = await cubeMfa.approvalFido(mfaId: string);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## addFido
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
const res = await cubeMfa.addFido(fidoName, receipt);
|
|
54
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tomo-inc/cubist-wallet-sdk",
|
|
3
|
+
"version": "0.0.4",
|
|
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
|
+
"axios": "^1.11.0",
|
|
22
|
+
"crypto-js": "^4.2.0",
|
|
23
|
+
"@tomo-inc/wallet-utils": "0.0.2"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@types/crypto-js": "^4.2.2",
|
|
27
|
+
"@types/node": "^20.0.0",
|
|
28
|
+
"@types/supertest": "^2.0.12",
|
|
29
|
+
"supertest": "^6.3.0",
|
|
30
|
+
"tsup": "^8.0.0",
|
|
31
|
+
"tsx": "^4.19.2",
|
|
32
|
+
"typescript": "^5.0.0",
|
|
33
|
+
"@vitest/browser": "^3.2.4",
|
|
34
|
+
"playwright": "^1.44.1",
|
|
35
|
+
"vitest": "^3.2.4"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsup"
|
|
39
|
+
}
|
|
40
|
+
}
|
package/project.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cubist-wallet-sdk",
|
|
3
|
+
"sourceRoot": "packages/cubist-wallet-sdk/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/cubist-wallet-sdk"
|
|
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/cubist-wallet-sdk"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"lint": {
|
|
22
|
+
"executor": "nx:run-commands",
|
|
23
|
+
"options": {
|
|
24
|
+
"command": "eslint src/**/*.ts",
|
|
25
|
+
"cwd": "packages/cubist-wallet-sdk"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"lint:fix": {
|
|
29
|
+
"executor": "nx:run-commands",
|
|
30
|
+
"options": {
|
|
31
|
+
"command": "eslint src/**/*.ts --fix",
|
|
32
|
+
"cwd": "packages/cubist-wallet-sdk"
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"format": {
|
|
36
|
+
"executor": "nx:run-commands",
|
|
37
|
+
"options": {
|
|
38
|
+
"command": "prettier --write \"src/**/*.{ts,tsx}\"",
|
|
39
|
+
"cwd": "packages/cubist-wallet-sdk"
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"test": {
|
|
43
|
+
"executor": "nx:run-commands",
|
|
44
|
+
"outputs": ["{projectRoot}/coverage"],
|
|
45
|
+
"options": {
|
|
46
|
+
"command": "vitest run",
|
|
47
|
+
"cwd": "packages/cubist-wallet-sdk"
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"test:watch": {
|
|
51
|
+
"executor": "nx:run-commands",
|
|
52
|
+
"options": {
|
|
53
|
+
"command": "vitest",
|
|
54
|
+
"cwd": "packages/cubist-wallet-sdk"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"tags": ["npm:private", "scope:cubist-wallet-sdk", "type:library"]
|
|
59
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { CubeMfaService } from "../cube-mfa";
|
|
3
|
+
|
|
4
|
+
describe("CubeMfaService", () => {
|
|
5
|
+
describe("extractSecretFromOtpUri", () => {
|
|
6
|
+
it("should extract secret from valid OTP URI", () => {
|
|
7
|
+
const otpUri = "otpauth://totp/productName:?secret=6JOGJWLATLOOJB7EFULZF73DDLQGUE4F&issuer=productName";
|
|
8
|
+
const secret = CubeMfaService.extractSecretFromOtpUri(otpUri);
|
|
9
|
+
expect(secret).toBe("6JOGJWLATLOOJB7EFULZF73DDLQGUE4F");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it("should extract secret from OTP URI with different format", () => {
|
|
13
|
+
const otpUri = "otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example";
|
|
14
|
+
const secret = CubeMfaService.extractSecretFromOtpUri(otpUri);
|
|
15
|
+
expect(secret).toBe("JBSWY3DPEHPK3PXP");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("should extract secret from OTP URI with only secret parameter", () => {
|
|
19
|
+
const otpUri = "otpauth://totp/productName:?secret=ABCDEFGHIJKLMNOP";
|
|
20
|
+
const secret = CubeMfaService.extractSecretFromOtpUri(otpUri);
|
|
21
|
+
expect(secret).toBe("ABCDEFGHIJKLMNOP");
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("should return null for invalid OTP URI", () => {
|
|
25
|
+
const otpUri = "https://example.com?secret=123";
|
|
26
|
+
const secret = CubeMfaService.extractSecretFromOtpUri(otpUri);
|
|
27
|
+
expect(secret).toBeNull();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("should return null for OTP URI without secret parameter", () => {
|
|
31
|
+
const otpUri = "otpauth://totp/productName:?issuer=productName";
|
|
32
|
+
const secret = CubeMfaService.extractSecretFromOtpUri(otpUri);
|
|
33
|
+
expect(secret).toBeNull();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should return null for empty string", () => {
|
|
37
|
+
const secret = CubeMfaService.extractSecretFromOtpUri("");
|
|
38
|
+
expect(secret).toBeNull();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should return null for null input", () => {
|
|
42
|
+
const secret = CubeMfaService.extractSecretFromOtpUri(null as any);
|
|
43
|
+
expect(secret).toBeNull();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("should return null for undefined input", () => {
|
|
47
|
+
const secret = CubeMfaService.extractSecretFromOtpUri(undefined as any);
|
|
48
|
+
expect(secret).toBeNull();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it("should handle malformed URI with regex fallback", () => {
|
|
52
|
+
const otpUri = "otpauth://totp/productName:?secret=6JOGJWLATLOOJB7EFULZF73DDLQGUE4F&issuer=productName";
|
|
53
|
+
const secret = CubeMfaService.extractSecretFromOtpUri(otpUri);
|
|
54
|
+
expect(secret).toBe("6JOGJWLATLOOJB7EFULZF73DDLQGUE4F");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("should handle URI with multiple parameters", () => {
|
|
58
|
+
const otpUri =
|
|
59
|
+
"otpauth://totp/productName:?algorithm=SHA1&digits=6&period=30&secret=6JOGJWLATLOOJB7EFULZF73DDLQGUE4F&issuer=productName";
|
|
60
|
+
const secret = CubeMfaService.extractSecretFromOtpUri(otpUri);
|
|
61
|
+
expect(secret).toBe("6JOGJWLATLOOJB7EFULZF73DDLQGUE4F");
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
});
|
package/src/api.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { TomoApiDomains } from "@tomo-inc/wallet-utils";
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
import { generateCubeSignature } from "./signature";
|
|
4
|
+
import { CubeConfig } from "./types";
|
|
5
|
+
|
|
6
|
+
function getTomoApi(config: CubeConfig) {
|
|
7
|
+
const { tomoStage = "dev", tomoClientId, jwtToken } = config;
|
|
8
|
+
const tomoApi = axios.create({
|
|
9
|
+
baseURL: `${TomoApiDomains[tomoStage] || TomoApiDomains.dev}/user`,
|
|
10
|
+
timeout: 20000,
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
tomoApi.interceptors.request.use(
|
|
14
|
+
async (config) => {
|
|
15
|
+
config.headers["client-id"] = tomoClientId;
|
|
16
|
+
if (jwtToken) {
|
|
17
|
+
config.headers["Authorization"] = `Bearer ${jwtToken}`;
|
|
18
|
+
}
|
|
19
|
+
return config;
|
|
20
|
+
},
|
|
21
|
+
(error) => {
|
|
22
|
+
if (error?.response?.status === 401) {
|
|
23
|
+
return Promise.reject(error);
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
return tomoApi;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const addUserToCube = async (req: any, config: CubeConfig) => {
|
|
32
|
+
// sign for api
|
|
33
|
+
const { iss, sub } = req;
|
|
34
|
+
const cubeSalt = config.cubeSalt || "";
|
|
35
|
+
if (!cubeSalt) {
|
|
36
|
+
throw new Error("tomoStage error.");
|
|
37
|
+
}
|
|
38
|
+
const { timestamp, signature } = await generateCubeSignature({ iss, sub }, cubeSalt);
|
|
39
|
+
const signedReq = { ...req, timestamp, signature };
|
|
40
|
+
|
|
41
|
+
// send signatrue
|
|
42
|
+
const tomoApi = getTomoApi(config);
|
|
43
|
+
const res = await tomoApi.post("/api/login/loginByCubistV2", signedReq);
|
|
44
|
+
return res?.data?.data || {};
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const updateUserInfo = async (
|
|
48
|
+
userInfo: { nickname: string; avatar: string },
|
|
49
|
+
config: CubeConfig,
|
|
50
|
+
): Promise<boolean> => {
|
|
51
|
+
// send signatrue
|
|
52
|
+
const tomoApi = getTomoApi(config);
|
|
53
|
+
|
|
54
|
+
const res = await tomoApi.post("/api/user/getUserInfo");
|
|
55
|
+
const userInfoBefore = res?.data?.data.user || {};
|
|
56
|
+
|
|
57
|
+
const nickname = userInfo.nickname.trim();
|
|
58
|
+
userInfo = { ...userInfoBefore, ...userInfo, nickname, username: nickname };
|
|
59
|
+
|
|
60
|
+
const result = await tomoApi.post("/api/setting/updateUserInfo", userInfo, {
|
|
61
|
+
headers: {},
|
|
62
|
+
});
|
|
63
|
+
return !!result?.data?.success;
|
|
64
|
+
};
|
package/src/const.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { CubeStage } from "@tomo-inc/wallet-utils";
|
|
2
|
+
import { AccountType, BizType } from "types";
|
|
3
|
+
|
|
4
|
+
// export const CUBE_RP_ID = "cubist.dev";
|
|
5
|
+
export const SEED_PHRASE_EXPORT_DURATION = 48; //hour
|
|
6
|
+
export const EXPORT_TIME_FORMAT = "yyyy-MM-dd hh:mm:ss";
|
|
7
|
+
export const OIDC_TOKEN_LIFETIME = 30 * 24 * 60 * 60; //seconds
|
|
8
|
+
export const CUBE_LIFETIME = {
|
|
9
|
+
auth: OIDC_TOKEN_LIFETIME,
|
|
10
|
+
refresh: OIDC_TOKEN_LIFETIME * 2,
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const CUBE_SCOPES = [
|
|
14
|
+
"sign:*",
|
|
15
|
+
"export:*",
|
|
16
|
+
"manage:session:extend",
|
|
17
|
+
|
|
18
|
+
"manage:mfa:*",
|
|
19
|
+
"manage:mfa:register:email",
|
|
20
|
+
"manage:mfa:register:fido",
|
|
21
|
+
"manage:mfa:unregister:fido",
|
|
22
|
+
"manage:mfa:register:totp",
|
|
23
|
+
"manage:mfa:unregister:totp",
|
|
24
|
+
|
|
25
|
+
"manage:export:user:delete",
|
|
26
|
+
"manage:export:user:list",
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
export const EXPORT_KEY_PREFIX = "Key#Mnemonic_";
|
|
30
|
+
|
|
31
|
+
export const ISS_MAP: Record<string, AccountType> = {
|
|
32
|
+
"https://accounts.google.com": "google",
|
|
33
|
+
"https://shim.oauth2.cubist.dev/twitter": "x",
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
export const CUBE_SALTS = {
|
|
37
|
+
prod: "xnPWRJT5XG2WyevuydMjMpZq",
|
|
38
|
+
pre: "xnPWRJT5XG2WyevuydMjMpZq",
|
|
39
|
+
dev: "dev@tomo",
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export const getMfaInfoConfig = (cubeStage: CubeStage) => {
|
|
43
|
+
const lastTime = cubeStage === "prod" ? 172800 : 300;
|
|
44
|
+
const AllowedMfaTypes = {
|
|
45
|
+
Login: ["Fido", "EmailOtp", "Totp"],
|
|
46
|
+
AddIdentity: ["Fido", "EmailOtp", "Totp"],
|
|
47
|
+
RegisterMfa: ["Fido", `EmailOtp#${lastTime}`, "Totp"],
|
|
48
|
+
Export: ["Fido", "Totp"],
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
//bindSocailAccount: = ["Fido", "EmailOtp", "Totp"]
|
|
52
|
+
const MfaRequestConfig: Record<BizType, { method: string; path: string; _allowed_mfa_types: string[] }> = {
|
|
53
|
+
createSession: {
|
|
54
|
+
method: "POST",
|
|
55
|
+
path: "/session/",
|
|
56
|
+
_allowed_mfa_types: AllowedMfaTypes.Login,
|
|
57
|
+
},
|
|
58
|
+
addFido: {
|
|
59
|
+
method: "POST",
|
|
60
|
+
path: "/user/me/fido/",
|
|
61
|
+
_allowed_mfa_types: AllowedMfaTypes.RegisterMfa,
|
|
62
|
+
},
|
|
63
|
+
deleteFido: {
|
|
64
|
+
method: "DELETE",
|
|
65
|
+
path: "/user/me/fido/",
|
|
66
|
+
_allowed_mfa_types: AllowedMfaTypes.RegisterMfa,
|
|
67
|
+
},
|
|
68
|
+
registerTotp: {
|
|
69
|
+
method: "POST",
|
|
70
|
+
path: "/user/me/totp/",
|
|
71
|
+
_allowed_mfa_types: AllowedMfaTypes.RegisterMfa,
|
|
72
|
+
},
|
|
73
|
+
deleteTotp: {
|
|
74
|
+
method: "DELETE",
|
|
75
|
+
path: "/user/me/totp/",
|
|
76
|
+
_allowed_mfa_types: AllowedMfaTypes.RegisterMfa,
|
|
77
|
+
},
|
|
78
|
+
registerEmailOtp: {
|
|
79
|
+
method: "POST",
|
|
80
|
+
path: "/user/me/email/",
|
|
81
|
+
_allowed_mfa_types: AllowedMfaTypes.RegisterMfa,
|
|
82
|
+
},
|
|
83
|
+
initExport: {
|
|
84
|
+
method: "POST",
|
|
85
|
+
path: "/user/me/export/",
|
|
86
|
+
_allowed_mfa_types: AllowedMfaTypes.Export,
|
|
87
|
+
},
|
|
88
|
+
completeExport: {
|
|
89
|
+
method: "PATCH",
|
|
90
|
+
path: "/user/me/export/",
|
|
91
|
+
_allowed_mfa_types: AllowedMfaTypes.Export,
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
return MfaRequestConfig;
|
|
96
|
+
};
|