@idos-network/kwil-infra 3.0.0 → 4.0.0
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 +27 -0
- package/dist/actions/index.d.mts +2 -2
- package/dist/actions/index.mjs +1 -1
- package/dist/actions/index.mjs.map +1 -1
- package/dist/facesign/index.d.mts +2 -2
- package/dist/facesign/index.mjs +1 -1
- package/dist/facesign-signer-CTPPUFxT.mjs +2 -0
- package/dist/facesign-signer-CTPPUFxT.mjs.map +1 -0
- package/dist/{facesign-signer-5I8juA7p.d.mts → facesign-signer-Dqh899om.d.mts} +3 -2
- package/dist/facesign-signer-Dqh899om.d.mts.map +1 -0
- package/dist/{index-Bqz6trCk.d.mts → index-Dwonqvwu.d.mts} +33 -20
- package/dist/index-Dwonqvwu.d.mts.map +1 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/nep413-D_D-6Yf6.mjs +2 -0
- package/dist/nep413-D_D-6Yf6.mjs.map +1 -0
- package/dist/signature-verification/index.d.mts +1 -1
- package/dist/signature-verification/index.mjs +1 -1
- package/dist/signature-verification/index.mjs.map +1 -1
- package/dist/xrp/utils.d.mts +41 -41
- package/dist/xrp/utils.d.mts.map +1 -1
- package/package.json +3 -3
- package/src/actions/actions.test.ts +18 -2
- package/src/actions/actions.ts +76 -19
- package/src/facesign/facesign-signer.test.ts +58 -1
- package/src/facesign/facesign-signer.ts +8 -0
- package/src/facesign/index.ts +1 -1
- package/src/mm-token/create-mm-token-kwil-signer.test.ts +0 -16
- package/src/mm-token/create-mm-token-kwil-signer.ts +0 -5
- package/src/near/create-near-wallet-kwil-signer.test.ts +218 -0
- package/src/near/create-near-wallet-kwil-signer.ts +121 -28
- package/src/near/nep413.ts +47 -37
- package/src/near/signature-verification.test.ts +35 -0
- package/src/near/signature-verification.ts +15 -36
- package/dist/facesign-signer-5I8juA7p.d.mts.map +0 -1
- package/dist/facesign-signer-z5T312uQ.mjs +0 -2
- package/dist/facesign-signer-z5T312uQ.mjs.map +0 -1
- package/dist/index-Bqz6trCk.d.mts.map +0 -1
package/src/actions/actions.ts
CHANGED
|
@@ -12,6 +12,11 @@ export const WALLET_TYPES = ["EVM", "NEAR", "XRPL", "Stellar", "FaceSign", "MM"]
|
|
|
12
12
|
export type WalletType = (typeof WALLET_TYPES)[number];
|
|
13
13
|
|
|
14
14
|
export const walletTypeSchema: z.ZodType<WalletType> = z.enum(WALLET_TYPES);
|
|
15
|
+
export const ADD_WALLET_TYPES = ["EVM", "NEAR", "XRPL", "Stellar", "FaceSign"] as const;
|
|
16
|
+
|
|
17
|
+
export type AddWalletType = (typeof ADD_WALLET_TYPES)[number];
|
|
18
|
+
|
|
19
|
+
export const addWalletTypeSchema: z.ZodType<AddWalletType> = z.enum(ADD_WALLET_TYPES);
|
|
15
20
|
export const ENCRYPTION_PASSWORD_STORES = ["user", "mpc", "mm"] as const;
|
|
16
21
|
|
|
17
22
|
export type EncryptionPasswordStore = (typeof ENCRYPTION_PASSWORD_STORES)[number];
|
|
@@ -165,6 +170,20 @@ export const actionSchema: Record<string, ActionSchemaElement[]> = {
|
|
|
165
170
|
type: DataType.Uuid,
|
|
166
171
|
},
|
|
167
172
|
],
|
|
173
|
+
credential_broader_message: [
|
|
174
|
+
{
|
|
175
|
+
name: "public_notes_signature",
|
|
176
|
+
type: DataType.Text,
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
name: "content_uri",
|
|
180
|
+
type: DataType.Text,
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: "recipient_wallet",
|
|
184
|
+
type: DataType.Text,
|
|
185
|
+
},
|
|
186
|
+
],
|
|
168
187
|
create_preliminary_credential: [
|
|
169
188
|
{
|
|
170
189
|
name: "request_id",
|
|
@@ -736,7 +755,7 @@ export type GetUserAsInserterOutput = z.infer<typeof GetUserAsInserterOutputSche
|
|
|
736
755
|
export async function getUserAsInserter(
|
|
737
756
|
kwilClient: KwilActionClient,
|
|
738
757
|
params: GetUserAsInserterInput,
|
|
739
|
-
): Promise<GetUserAsInserterOutput> {
|
|
758
|
+
): Promise<GetUserAsInserterOutput | undefined> {
|
|
740
759
|
const inputs = GetUserAsInserterInputSchema.parse(params);
|
|
741
760
|
return await kwilClient
|
|
742
761
|
.call<GetUserAsInserterOutput[]>({
|
|
@@ -792,13 +811,13 @@ export async function upsertWalletAsInserter(
|
|
|
792
811
|
|
|
793
812
|
export const AddWalletMessageInputSchema: z.ZodObject<{
|
|
794
813
|
address: z.ZodString;
|
|
795
|
-
wallet_type: z.ZodType<
|
|
814
|
+
wallet_type: z.ZodType<AddWalletType>;
|
|
796
815
|
user_id: z.ZodUUID;
|
|
797
816
|
not_before: z.ZodISODateTime;
|
|
798
817
|
not_after: z.ZodISODateTime;
|
|
799
818
|
}> = z.object({
|
|
800
819
|
address: z.string(),
|
|
801
|
-
wallet_type:
|
|
820
|
+
wallet_type: addWalletTypeSchema,
|
|
802
821
|
user_id: z.uuid(),
|
|
803
822
|
not_before: rfc3339DateTimeSchema,
|
|
804
823
|
not_after: rfc3339DateTimeSchema,
|
|
@@ -834,7 +853,7 @@ export const AddWalletInputSchema: z.ZodObject<{
|
|
|
834
853
|
id: z.ZodUUID;
|
|
835
854
|
address: z.ZodString;
|
|
836
855
|
public_key: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
837
|
-
wallet_type: z.ZodType<
|
|
856
|
+
wallet_type: z.ZodType<AddWalletType>;
|
|
838
857
|
not_before: z.ZodISODateTime;
|
|
839
858
|
not_after: z.ZodISODateTime;
|
|
840
859
|
signature: z.ZodString;
|
|
@@ -842,7 +861,7 @@ export const AddWalletInputSchema: z.ZodObject<{
|
|
|
842
861
|
id: z.uuid(),
|
|
843
862
|
address: z.string(),
|
|
844
863
|
public_key: z.string().nullish(),
|
|
845
|
-
wallet_type:
|
|
864
|
+
wallet_type: addWalletTypeSchema,
|
|
846
865
|
not_before: rfc3339DateTimeSchema,
|
|
847
866
|
not_after: rfc3339DateTimeSchema,
|
|
848
867
|
signature: z.string(),
|
|
@@ -868,7 +887,7 @@ export const GetWalletsOutputSchema: z.ZodObject<{
|
|
|
868
887
|
address: z.ZodString;
|
|
869
888
|
public_key: z.ZodNullable<z.ZodString>;
|
|
870
889
|
wallet_type: z.ZodType<WalletType>;
|
|
871
|
-
message: z.ZodString
|
|
890
|
+
message: z.ZodNullable<z.ZodString>;
|
|
872
891
|
signature: z.ZodString;
|
|
873
892
|
inserter: z.ZodNullable<z.ZodString>;
|
|
874
893
|
}> = z.object({
|
|
@@ -877,7 +896,7 @@ export const GetWalletsOutputSchema: z.ZodObject<{
|
|
|
877
896
|
address: z.string(),
|
|
878
897
|
public_key: z.string().nullable(),
|
|
879
898
|
wallet_type: walletTypeSchema,
|
|
880
|
-
message: z.string(),
|
|
899
|
+
message: z.string().nullable(),
|
|
881
900
|
signature: z.string(),
|
|
882
901
|
inserter: z.string().nullable(),
|
|
883
902
|
});
|
|
@@ -911,6 +930,43 @@ export async function removeWallet(
|
|
|
911
930
|
});
|
|
912
931
|
}
|
|
913
932
|
|
|
933
|
+
export const CredentialBroaderMessageInputSchema: z.ZodObject<{
|
|
934
|
+
public_notes_signature: z.ZodString;
|
|
935
|
+
content_uri: z.ZodString;
|
|
936
|
+
recipient_wallet: z.ZodString;
|
|
937
|
+
}> = z.object({
|
|
938
|
+
public_notes_signature: z.string(),
|
|
939
|
+
content_uri: blobContentUriSchema,
|
|
940
|
+
recipient_wallet: z.string(),
|
|
941
|
+
});
|
|
942
|
+
|
|
943
|
+
export type CredentialBroaderMessageInput = z.infer<typeof CredentialBroaderMessageInputSchema>;
|
|
944
|
+
|
|
945
|
+
export const CredentialBroaderMessageOutputSchema: z.ZodObject<{
|
|
946
|
+
message: z.ZodString;
|
|
947
|
+
}> = z.object({
|
|
948
|
+
message: z.string(),
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
export type CredentialBroaderMessageOutput = z.infer<typeof CredentialBroaderMessageOutputSchema>;
|
|
952
|
+
|
|
953
|
+
/** CREDENTIAL ACTIONS */
|
|
954
|
+
export async function credentialBroaderMessage(
|
|
955
|
+
kwilClient: KwilActionClient,
|
|
956
|
+
params: CredentialBroaderMessageInput,
|
|
957
|
+
): Promise<CredentialBroaderMessageOutput> {
|
|
958
|
+
const inputs = CredentialBroaderMessageInputSchema.parse(params);
|
|
959
|
+
return await kwilClient
|
|
960
|
+
.call<CredentialBroaderMessageOutput[]>({
|
|
961
|
+
name: "credential_broader_message",
|
|
962
|
+
inputs,
|
|
963
|
+
})
|
|
964
|
+
.then((result) => {
|
|
965
|
+
if (!result[0]) throw new Error("credential_broader_message returned no rows");
|
|
966
|
+
return result[0];
|
|
967
|
+
});
|
|
968
|
+
}
|
|
969
|
+
|
|
914
970
|
export const CreatePreliminaryCredentialInputSchema: z.ZodObject<{
|
|
915
971
|
request_id: z.ZodUUID;
|
|
916
972
|
credential_id: z.ZodUUID;
|
|
@@ -939,7 +995,6 @@ export type CreatePreliminaryCredentialInput = z.infer<
|
|
|
939
995
|
typeof CreatePreliminaryCredentialInputSchema
|
|
940
996
|
>;
|
|
941
997
|
|
|
942
|
-
/** CREDENTIAL ACTIONS */
|
|
943
998
|
export async function createPreliminaryCredential(
|
|
944
999
|
kwilClient: KwilActionClient,
|
|
945
1000
|
params: CreatePreliminaryCredentialInput,
|
|
@@ -1260,7 +1315,7 @@ export type CredentialExistAsInserterOutput = z.infer<typeof CredentialExistAsIn
|
|
|
1260
1315
|
export async function credentialExistAsInserter(
|
|
1261
1316
|
kwilClient: KwilActionClient,
|
|
1262
1317
|
params: CredentialExistAsInserterInput,
|
|
1263
|
-
): Promise<CredentialExistAsInserterOutput> {
|
|
1318
|
+
): Promise<CredentialExistAsInserterOutput | undefined> {
|
|
1264
1319
|
const inputs = CredentialExistAsInserterInputSchema.parse(params);
|
|
1265
1320
|
return await kwilClient
|
|
1266
1321
|
.call<CredentialExistAsInserterOutput[]>({
|
|
@@ -1380,7 +1435,7 @@ export type GetSiblingCredentialIdOutput = z.infer<typeof GetSiblingCredentialId
|
|
|
1380
1435
|
export async function getSiblingCredentialId(
|
|
1381
1436
|
kwilClient: KwilActionClient,
|
|
1382
1437
|
params: GetSiblingCredentialIdInput,
|
|
1383
|
-
): Promise<GetSiblingCredentialIdOutput> {
|
|
1438
|
+
): Promise<GetSiblingCredentialIdOutput | undefined> {
|
|
1384
1439
|
const inputs = GetSiblingCredentialIdInputSchema.parse(params);
|
|
1385
1440
|
return await kwilClient
|
|
1386
1441
|
.call<GetSiblingCredentialIdOutput[]>({
|
|
@@ -1573,7 +1628,7 @@ export type DwgMessageOutput = z.infer<typeof DwgMessageOutputSchema>;
|
|
|
1573
1628
|
export async function dwgMessage(
|
|
1574
1629
|
kwilClient: KwilActionClient,
|
|
1575
1630
|
params: DwgMessageInput,
|
|
1576
|
-
): Promise<DwgMessageOutput> {
|
|
1631
|
+
): Promise<DwgMessageOutput | undefined> {
|
|
1577
1632
|
const inputs = DwgMessageInputSchema.parse(params);
|
|
1578
1633
|
return await kwilClient
|
|
1579
1634
|
.call<DwgMessageOutput[]>({
|
|
@@ -1708,7 +1763,7 @@ export type GetAccessGrantsGrantedCountOutput = z.infer<
|
|
|
1708
1763
|
export async function getAccessGrantsGrantedCount(
|
|
1709
1764
|
kwilClient: KwilActionClient,
|
|
1710
1765
|
params: GetAccessGrantsGrantedCountInput,
|
|
1711
|
-
): Promise<GetAccessGrantsGrantedCountOutput> {
|
|
1766
|
+
): Promise<GetAccessGrantsGrantedCountOutput | undefined> {
|
|
1712
1767
|
const inputs = GetAccessGrantsGrantedCountInputSchema.parse(params);
|
|
1713
1768
|
return await kwilClient
|
|
1714
1769
|
.call<GetAccessGrantsGrantedCountOutput[]>({
|
|
@@ -1737,7 +1792,7 @@ export type HasLockedAccessGrantsOutput = z.infer<typeof HasLockedAccessGrantsOu
|
|
|
1737
1792
|
export async function hasLockedAccessGrants(
|
|
1738
1793
|
kwilClient: KwilActionClient,
|
|
1739
1794
|
params: HasLockedAccessGrantsInput,
|
|
1740
|
-
): Promise<HasLockedAccessGrantsOutput> {
|
|
1795
|
+
): Promise<HasLockedAccessGrantsOutput | undefined> {
|
|
1741
1796
|
const inputs = HasLockedAccessGrantsInputSchema.parse(params);
|
|
1742
1797
|
return await kwilClient
|
|
1743
1798
|
.call<HasLockedAccessGrantsOutput[]>({
|
|
@@ -1815,7 +1870,7 @@ export type HasProfileOutput = z.infer<typeof HasProfileOutputSchema>;
|
|
|
1815
1870
|
export async function hasProfile(
|
|
1816
1871
|
kwilClient: KwilActionClient,
|
|
1817
1872
|
params: HasProfileInput,
|
|
1818
|
-
): Promise<HasProfileOutput> {
|
|
1873
|
+
): Promise<HasProfileOutput | undefined> {
|
|
1819
1874
|
const inputs = HasProfileInputSchema.parse(params);
|
|
1820
1875
|
return await kwilClient
|
|
1821
1876
|
.call<HasProfileOutput[]>(
|
|
@@ -1867,7 +1922,7 @@ export type CheckCallerPayerOutput = z.infer<typeof CheckCallerPayerOutputSchema
|
|
|
1867
1922
|
export async function checkCallerPayer(
|
|
1868
1923
|
kwilClient: KwilActionClient,
|
|
1869
1924
|
params: CheckCallerPayerInput,
|
|
1870
|
-
): Promise<CheckCallerPayerOutput> {
|
|
1925
|
+
): Promise<CheckCallerPayerOutput | undefined> {
|
|
1871
1926
|
const inputs = CheckCallerPayerInputSchema.parse(params);
|
|
1872
1927
|
return await kwilClient
|
|
1873
1928
|
.call<CheckCallerPayerOutput[]>({
|
|
@@ -1918,7 +1973,7 @@ export type CheckBalanceOutput = z.infer<typeof CheckBalanceOutputSchema>;
|
|
|
1918
1973
|
export async function checkBalance(
|
|
1919
1974
|
kwilClient: KwilActionClient,
|
|
1920
1975
|
params: CheckBalanceInput,
|
|
1921
|
-
): Promise<CheckBalanceOutput> {
|
|
1976
|
+
): Promise<CheckBalanceOutput | undefined> {
|
|
1922
1977
|
const inputs = CheckBalanceInputSchema.parse(params);
|
|
1923
1978
|
return await kwilClient
|
|
1924
1979
|
.call<CheckBalanceOutput[]>({
|
|
@@ -1949,7 +2004,7 @@ export type GetWalletWithBalanceOutput = z.infer<typeof GetWalletWithBalanceOutp
|
|
|
1949
2004
|
export async function getWalletWithBalance(
|
|
1950
2005
|
kwilClient: KwilActionClient,
|
|
1951
2006
|
params: GetWalletWithBalanceInput,
|
|
1952
|
-
): Promise<GetWalletWithBalanceOutput> {
|
|
2007
|
+
): Promise<GetWalletWithBalanceOutput | undefined> {
|
|
1953
2008
|
const inputs = GetWalletWithBalanceInputSchema.parse(params);
|
|
1954
2009
|
return await kwilClient
|
|
1955
2010
|
.call<GetWalletWithBalanceOutput[]>({
|
|
@@ -1989,7 +2044,9 @@ export const GetAllowanceOutputSchema: z.ZodObject<{
|
|
|
1989
2044
|
export type GetAllowanceOutput = z.infer<typeof GetAllowanceOutputSchema>;
|
|
1990
2045
|
|
|
1991
2046
|
/** FaceSign should not be able to call this */
|
|
1992
|
-
export async function getAllowance(
|
|
2047
|
+
export async function getAllowance(
|
|
2048
|
+
kwilClient: KwilActionClient,
|
|
2049
|
+
): Promise<GetAllowanceOutput | undefined> {
|
|
1993
2050
|
return await kwilClient
|
|
1994
2051
|
.call<GetAllowanceOutput[]>({
|
|
1995
2052
|
name: "get_allowance",
|
|
@@ -2045,7 +2102,7 @@ export type GetIssuerFeeOutput = z.infer<typeof GetIssuerFeeOutputSchema>;
|
|
|
2045
2102
|
export async function getIssuerFee(
|
|
2046
2103
|
kwilClient: KwilActionClient,
|
|
2047
2104
|
params: GetIssuerFeeInput,
|
|
2048
|
-
): Promise<GetIssuerFeeOutput> {
|
|
2105
|
+
): Promise<GetIssuerFeeOutput | undefined> {
|
|
2049
2106
|
const inputs = GetIssuerFeeInputSchema.parse(params);
|
|
2050
2107
|
return await kwilClient
|
|
2051
2108
|
.call<GetIssuerFeeOutput[]>({
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
4
4
|
|
|
5
|
-
import { FaceSignSignerProvider } from "./facesign-signer";
|
|
5
|
+
import { FaceSignSignerProvider, resetStoredFaceSignKey } from "./facesign-signer";
|
|
6
6
|
|
|
7
7
|
const ENCLAVE_ORIGIN = "https://facesign.example";
|
|
8
8
|
|
|
@@ -21,6 +21,63 @@ function iframeWindow(): Window {
|
|
|
21
21
|
return iframe.contentWindow;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
describe("resetStoredFaceSignKey", () => {
|
|
25
|
+
afterEach(() => {
|
|
26
|
+
vi.useRealTimers();
|
|
27
|
+
document.body.replaceChildren();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
async function readyProvider(hasKey: boolean) {
|
|
31
|
+
const signer = provider();
|
|
32
|
+
const posted: unknown[] = [];
|
|
33
|
+
const pending = resetStoredFaceSignKey(signer);
|
|
34
|
+
|
|
35
|
+
await vi.waitFor(() => {
|
|
36
|
+
expect(document.querySelector("iframe")).not.toBeNull();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
vi.spyOn(iframeWindow(), "postMessage").mockImplementation((message) => {
|
|
40
|
+
posted.push(message);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
window.dispatchEvent(
|
|
44
|
+
new MessageEvent("message", {
|
|
45
|
+
origin: ENCLAVE_ORIGIN,
|
|
46
|
+
data: { type: "facesign_ready", hasKey },
|
|
47
|
+
}),
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
return { signer, posted, pending };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
it("deletes a stored key and tears the provider down", async () => {
|
|
54
|
+
const { posted, pending } = await readyProvider(true);
|
|
55
|
+
|
|
56
|
+
await vi.waitFor(() => {
|
|
57
|
+
expect(posted).toContainEqual({ type: "reset", data: { id: 1 } });
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
window.dispatchEvent(
|
|
61
|
+
new MessageEvent("message", {
|
|
62
|
+
origin: ENCLAVE_ORIGIN,
|
|
63
|
+
data: { type: "reset_complete", data: { id: 1, ok: true } },
|
|
64
|
+
}),
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
await expect(pending).resolves.toBe(true);
|
|
68
|
+
expect(document.querySelector("iframe")).toBeNull();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("leaves a provider with no stored key ready to scan", async () => {
|
|
72
|
+
const { signer, posted, pending } = await readyProvider(false);
|
|
73
|
+
|
|
74
|
+
await expect(pending).resolves.toBe(false);
|
|
75
|
+
expect(posted).toEqual([]);
|
|
76
|
+
expect(document.querySelector("iframe")).not.toBeNull();
|
|
77
|
+
signer.destroy();
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
24
81
|
describe("FaceSignSignerProvider.reset", () => {
|
|
25
82
|
afterEach(() => {
|
|
26
83
|
vi.useRealTimers();
|
|
@@ -18,6 +18,14 @@ interface SessionResponse {
|
|
|
18
18
|
address: string;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
// Browser-stored key is not the active idOS profile. Delete it before add-wallet can sign. reset() destroys this provider.
|
|
22
|
+
export async function resetStoredFaceSignKey(provider: FaceSignSignerProvider): Promise<boolean> {
|
|
23
|
+
const { hasKey } = await provider.preload();
|
|
24
|
+
if (!hasKey) return false;
|
|
25
|
+
await provider.reset();
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
|
|
21
29
|
export class FaceSignSignerProvider {
|
|
22
30
|
#iframe: HTMLIFrameElement | null = null;
|
|
23
31
|
#container: HTMLDialogElement | null = null;
|
package/src/facesign/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export type { FaceSignMetadata, FaceSignSignerProviderOptions } from "./facesign-signer";
|
|
2
|
-
export { FaceSignSignerProvider } from "./facesign-signer";
|
|
2
|
+
export { FaceSignSignerProvider, resetStoredFaceSignKey } from "./facesign-signer";
|
|
@@ -4,7 +4,6 @@ import { describe, expect, it } from "vitest";
|
|
|
4
4
|
import {
|
|
5
5
|
createMmTokenAuth,
|
|
6
6
|
isMmTokenAuth,
|
|
7
|
-
mmTokenCredentialContentUri,
|
|
8
7
|
mmTokenStorageId,
|
|
9
8
|
type MmTokenEnvelope,
|
|
10
9
|
} from "./create-mm-token-kwil-signer.js";
|
|
@@ -116,21 +115,6 @@ describe("createMmTokenAuth", () => {
|
|
|
116
115
|
expect(mmTokenStorageId(signer)).toBe("storage-abc");
|
|
117
116
|
});
|
|
118
117
|
|
|
119
|
-
it("builds the credential ukyc:// URI from a base64url-encoded token", () => {
|
|
120
|
-
const credentialId = "0198c21d-79cb-7000-8000-000000000001";
|
|
121
|
-
const encodedToken = encodeEnvelope(createEnvelope());
|
|
122
|
-
const signer = createMmTokenAuth(encodedToken);
|
|
123
|
-
const credential = {
|
|
124
|
-
id: credentialId,
|
|
125
|
-
content_uri: mmTokenCredentialContentUri(signer, credentialId),
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
expect(credential).toEqual({
|
|
129
|
-
id: credentialId,
|
|
130
|
-
content_uri: `ukyc://storage-abc/blobs/${credentialId}`,
|
|
131
|
-
});
|
|
132
|
-
});
|
|
133
|
-
|
|
134
118
|
it("fails when the token payload has no storage_id", () => {
|
|
135
119
|
const signer = createMmTokenAuth(
|
|
136
120
|
encodeEnvelope(
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { KwilSigner } from "@idos-network/kwil-js";
|
|
2
|
-
import { createUkycContentUri } from "@idos-network/utils/blob-gateway";
|
|
3
2
|
import {
|
|
4
3
|
base64UrlDecode,
|
|
5
4
|
base64UrlEncode,
|
|
@@ -58,10 +57,6 @@ export function mmTokenStorageId(signer: KwilSigner): string {
|
|
|
58
57
|
return storageId.trim();
|
|
59
58
|
}
|
|
60
59
|
|
|
61
|
-
export function mmTokenCredentialContentUri(signer: KwilSigner, credentialId: string): string {
|
|
62
|
-
return createUkycContentUri(mmTokenStorageId(signer), credentialId);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
60
|
function isMmTokenEnvelope(value: unknown): value is MmTokenEnvelope {
|
|
66
61
|
return (
|
|
67
62
|
value !== null &&
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import type { Wallet as NearWallet } from "@near-wallet-selector/core";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
base64Encode,
|
|
5
|
+
borshSerialize,
|
|
6
|
+
bs58Encode,
|
|
7
|
+
bytesConcat,
|
|
8
|
+
sha256Hash,
|
|
9
|
+
} from "@idos-network/utils/codecs";
|
|
10
|
+
import { MemoryStore } from "@idos-network/utils/store";
|
|
11
|
+
import nacl from "tweetnacl";
|
|
12
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
13
|
+
|
|
14
|
+
import type { KwilActionClient } from "../create-kwil-client";
|
|
15
|
+
|
|
16
|
+
import { createNearWalletKwilSigner } from "./create-near-wallet-kwil-signer";
|
|
17
|
+
|
|
18
|
+
const fullAccess = vi.hoisted(() => ({
|
|
19
|
+
keys: [] as string[],
|
|
20
|
+
limitedKeys: [] as string[],
|
|
21
|
+
queries: 0,
|
|
22
|
+
lastRequest: undefined as { request_type?: string; account_id?: string } | undefined,
|
|
23
|
+
}));
|
|
24
|
+
|
|
25
|
+
vi.mock("near-api-js", () => ({
|
|
26
|
+
connect: async () => ({
|
|
27
|
+
connection: {
|
|
28
|
+
provider: {
|
|
29
|
+
query: async (request: { request_type?: string; account_id?: string }) => {
|
|
30
|
+
fullAccess.queries += 1;
|
|
31
|
+
fullAccess.lastRequest = request;
|
|
32
|
+
return {
|
|
33
|
+
keys: [
|
|
34
|
+
...fullAccess.keys.map((public_key) => ({
|
|
35
|
+
public_key,
|
|
36
|
+
access_key: { permission: "FullAccess" },
|
|
37
|
+
})),
|
|
38
|
+
...fullAccess.limitedKeys.map((public_key) => ({
|
|
39
|
+
public_key,
|
|
40
|
+
access_key: {
|
|
41
|
+
permission: {
|
|
42
|
+
FunctionCall: {
|
|
43
|
+
allowance: "0",
|
|
44
|
+
receiver_id: "example.near",
|
|
45
|
+
method_names: [],
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
})),
|
|
50
|
+
],
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
}),
|
|
56
|
+
}));
|
|
57
|
+
|
|
58
|
+
const ACCOUNT = "victim.testnet";
|
|
59
|
+
const RECIPIENT = "idos.network";
|
|
60
|
+
const CALLBACK = "https://app.example/callback";
|
|
61
|
+
const MESSAGE = "idOS authentication";
|
|
62
|
+
const NEP413_TAG = 2147484061;
|
|
63
|
+
|
|
64
|
+
const nep413PayloadSchema = {
|
|
65
|
+
struct: {
|
|
66
|
+
message: "string",
|
|
67
|
+
nonce: { array: { type: "u8", len: 32 } },
|
|
68
|
+
recipient: "string",
|
|
69
|
+
callbackUrl: { option: "string" },
|
|
70
|
+
},
|
|
71
|
+
} as const;
|
|
72
|
+
|
|
73
|
+
const keyPair = nacl.sign.keyPair.fromSeed(new Uint8Array(32).fill(4));
|
|
74
|
+
const fragmentKey = `ed25519:${bs58Encode(keyPair.publicKey)}`;
|
|
75
|
+
const realKey = `ed25519:${bs58Encode(new Uint8Array(32).fill(1))}`;
|
|
76
|
+
|
|
77
|
+
const kwilClient = {
|
|
78
|
+
client: { auth: { logoutKGW: async () => {} } },
|
|
79
|
+
} as unknown as KwilActionClient;
|
|
80
|
+
|
|
81
|
+
function signNep413(nonce: Uint8Array, message = MESSAGE): string {
|
|
82
|
+
const hash = sha256Hash(
|
|
83
|
+
bytesConcat(
|
|
84
|
+
borshSerialize("u32", NEP413_TAG),
|
|
85
|
+
borshSerialize(nep413PayloadSchema, {
|
|
86
|
+
message,
|
|
87
|
+
nonce,
|
|
88
|
+
recipient: RECIPIENT,
|
|
89
|
+
callbackUrl: CALLBACK,
|
|
90
|
+
}),
|
|
91
|
+
),
|
|
92
|
+
);
|
|
93
|
+
return base64Encode(nacl.sign.detached(hash, keyPair.secretKey));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function signedRequest(): { nonce: Uint8Array; signature: string } {
|
|
97
|
+
for (let i = 0; i < 64; i++) {
|
|
98
|
+
const nonce = new Uint8Array(32).fill(i);
|
|
99
|
+
const signature = signNep413(nonce);
|
|
100
|
+
if (signature.includes("+")) return { nonce, signature };
|
|
101
|
+
}
|
|
102
|
+
throw new Error("expected a base64 signature containing '+'");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function signerPublicKey(fragment: {
|
|
106
|
+
accountId: string;
|
|
107
|
+
signature: string;
|
|
108
|
+
publicKey: string;
|
|
109
|
+
nonce: Uint8Array;
|
|
110
|
+
message?: string;
|
|
111
|
+
}): Promise<string> {
|
|
112
|
+
// Literal "+", not percent-encoded, so URLSearchParams would turn it into a space.
|
|
113
|
+
window.location.hash = `accountId=${fragment.accountId}&signature=${fragment.signature}&publicKey=${fragment.publicKey}`;
|
|
114
|
+
const store = new MemoryStore();
|
|
115
|
+
await store.set("signer-address", ACCOUNT);
|
|
116
|
+
await store.set("signer-public-key", realKey);
|
|
117
|
+
await store.set("sign-last-message", fragment.message ?? MESSAGE);
|
|
118
|
+
await store.set("sign-last-nonce", Array.from(fragment.nonce));
|
|
119
|
+
await store.set("sign-last-url", CALLBACK);
|
|
120
|
+
const wallet = { id: "my-near-wallet", signMessage: vi.fn() } as unknown as NearWallet;
|
|
121
|
+
|
|
122
|
+
const { publicKey } = await createNearWalletKwilSigner(wallet, ACCOUNT, store, kwilClient);
|
|
123
|
+
return publicKey;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
describe("createNearWalletKwilSigner my-near-wallet redirect", () => {
|
|
127
|
+
afterEach(() => {
|
|
128
|
+
window.location.hash = "";
|
|
129
|
+
fullAccess.keys = [];
|
|
130
|
+
fullAccess.limitedKeys = [];
|
|
131
|
+
fullAccess.queries = 0;
|
|
132
|
+
fullAccess.lastRequest = undefined;
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("accepts a fragment whose signature matches the saved request and a full-access key", async () => {
|
|
136
|
+
const { nonce, signature } = signedRequest();
|
|
137
|
+
fullAccess.keys = [realKey, fragmentKey];
|
|
138
|
+
|
|
139
|
+
expect(
|
|
140
|
+
await signerPublicKey({ accountId: ACCOUNT, signature, publicKey: fragmentKey, nonce }),
|
|
141
|
+
).toBe(fragmentKey);
|
|
142
|
+
expect(window.location.hash).toBe("");
|
|
143
|
+
expect(fullAccess.queries).toBe(1);
|
|
144
|
+
expect(fullAccess.lastRequest).toMatchObject({
|
|
145
|
+
request_type: "view_access_key_list",
|
|
146
|
+
account_id: ACCOUNT,
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("ignores a signature that does not match the saved request", async () => {
|
|
151
|
+
const { nonce, signature } = signedRequest();
|
|
152
|
+
fullAccess.keys = [realKey, fragmentKey];
|
|
153
|
+
|
|
154
|
+
expect(
|
|
155
|
+
await signerPublicKey({
|
|
156
|
+
accountId: ACCOUNT,
|
|
157
|
+
signature,
|
|
158
|
+
publicKey: fragmentKey,
|
|
159
|
+
nonce,
|
|
160
|
+
message: "different message",
|
|
161
|
+
}),
|
|
162
|
+
).toBe(realKey);
|
|
163
|
+
expect(fullAccess.queries).toBe(0);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("ignores a fragment for a different account", async () => {
|
|
167
|
+
const { nonce, signature } = signedRequest();
|
|
168
|
+
fullAccess.keys = [realKey, fragmentKey];
|
|
169
|
+
|
|
170
|
+
expect(
|
|
171
|
+
await signerPublicKey({
|
|
172
|
+
accountId: "attacker.testnet",
|
|
173
|
+
signature,
|
|
174
|
+
publicKey: fragmentKey,
|
|
175
|
+
nonce,
|
|
176
|
+
}),
|
|
177
|
+
).toBe(realKey);
|
|
178
|
+
expect(fullAccess.queries).toBe(0);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("ignores a public key that is not a full-access key of the account", async () => {
|
|
182
|
+
const { nonce, signature } = signedRequest();
|
|
183
|
+
fullAccess.keys = [realKey];
|
|
184
|
+
fullAccess.limitedKeys = [fragmentKey];
|
|
185
|
+
|
|
186
|
+
expect(
|
|
187
|
+
await signerPublicKey({ accountId: ACCOUNT, signature, publicKey: fragmentKey, nonce }),
|
|
188
|
+
).toBe(realKey);
|
|
189
|
+
expect(fullAccess.queries).toBe(1);
|
|
190
|
+
expect(fullAccess.lastRequest).toMatchObject({
|
|
191
|
+
request_type: "view_access_key_list",
|
|
192
|
+
account_id: ACCOUNT,
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("does not replay an untrusted fragment signature", async () => {
|
|
197
|
+
const { nonce } = signedRequest();
|
|
198
|
+
window.location.hash = `accountId=${ACCOUNT}&signature=sig&publicKey=${fragmentKey}`;
|
|
199
|
+
const store = new MemoryStore();
|
|
200
|
+
await store.set("signer-address", ACCOUNT);
|
|
201
|
+
await store.set("signer-public-key", realKey);
|
|
202
|
+
await store.set("sign-last-message", MESSAGE);
|
|
203
|
+
await store.set("sign-last-nonce", Array.from(nonce));
|
|
204
|
+
await store.set("sign-last-url", CALLBACK);
|
|
205
|
+
const original = vi.fn();
|
|
206
|
+
const wallet = { id: "my-near-wallet", signMessage: original } as unknown as NearWallet;
|
|
207
|
+
|
|
208
|
+
await createNearWalletKwilSigner(wallet, ACCOUNT, store, kwilClient);
|
|
209
|
+
void wallet.signMessage?.({
|
|
210
|
+
message: MESSAGE,
|
|
211
|
+
recipient: RECIPIENT,
|
|
212
|
+
nonce: Buffer.alloc(32),
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
await vi.waitFor(() => expect(original).toHaveBeenCalledOnce());
|
|
216
|
+
expect(original.mock.calls[0]?.[0].message).toBe(MESSAGE);
|
|
217
|
+
});
|
|
218
|
+
});
|