@privy-io/node 0.1.0-alpha.3 → 0.2.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.
Files changed (134) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/client.d.mts +1 -1
  3. package/client.d.ts +1 -1
  4. package/client.js +1 -1
  5. package/client.js.map +1 -1
  6. package/client.mjs +1 -1
  7. package/client.mjs.map +1 -1
  8. package/index.d.mts +6 -1
  9. package/index.d.mts.map +1 -1
  10. package/index.d.ts +6 -1
  11. package/index.d.ts.map +1 -1
  12. package/index.js +6 -2
  13. package/index.js.map +1 -1
  14. package/index.mjs +2 -1
  15. package/index.mjs.map +1 -1
  16. package/lib/auth.d.mts +68 -0
  17. package/lib/auth.d.mts.map +1 -0
  18. package/lib/auth.d.ts +68 -0
  19. package/lib/auth.d.ts.map +1 -0
  20. package/lib/auth.js +130 -0
  21. package/lib/auth.js.map +1 -0
  22. package/lib/auth.mjs +123 -0
  23. package/lib/auth.mjs.map +1 -0
  24. package/lib/cryptography.d.mts +3 -0
  25. package/lib/cryptography.d.mts.map +1 -1
  26. package/lib/cryptography.d.ts +3 -0
  27. package/lib/cryptography.d.ts.map +1 -1
  28. package/lib/cryptography.js +8 -1
  29. package/lib/cryptography.js.map +1 -1
  30. package/lib/cryptography.mjs +7 -1
  31. package/lib/cryptography.mjs.map +1 -1
  32. package/lib/identity-token.d.mts +15 -0
  33. package/lib/identity-token.d.mts.map +1 -0
  34. package/lib/identity-token.d.ts +15 -0
  35. package/lib/identity-token.d.ts.map +1 -0
  36. package/lib/identity-token.js +273 -0
  37. package/lib/identity-token.js.map +1 -0
  38. package/lib/identity-token.mjs +268 -0
  39. package/lib/identity-token.mjs.map +1 -0
  40. package/lib/user-utils.d.mts +22 -0
  41. package/lib/user-utils.d.mts.map +1 -0
  42. package/lib/user-utils.d.ts +22 -0
  43. package/lib/user-utils.d.ts.map +1 -0
  44. package/lib/user-utils.js +15 -0
  45. package/lib/user-utils.js.map +1 -0
  46. package/lib/user-utils.mjs +11 -0
  47. package/lib/user-utils.mjs.map +1 -0
  48. package/package.json +1 -1
  49. package/public-api/PrivyClient.d.mts +2 -1
  50. package/public-api/PrivyClient.d.mts.map +1 -1
  51. package/public-api/PrivyClient.d.ts +2 -1
  52. package/public-api/PrivyClient.d.ts.map +1 -1
  53. package/public-api/PrivyClient.js +10 -3
  54. package/public-api/PrivyClient.js.map +1 -1
  55. package/public-api/PrivyClient.mjs +10 -3
  56. package/public-api/PrivyClient.mjs.map +1 -1
  57. package/public-api/services/ethereum.d.mts.map +1 -1
  58. package/public-api/services/ethereum.d.ts.map +1 -1
  59. package/public-api/services/ethereum.js +0 -6
  60. package/public-api/services/ethereum.js.map +1 -1
  61. package/public-api/services/ethereum.mjs +0 -6
  62. package/public-api/services/ethereum.mjs.map +1 -1
  63. package/public-api/services/solana.d.mts.map +1 -1
  64. package/public-api/services/solana.d.ts.map +1 -1
  65. package/public-api/services/solana.js +0 -3
  66. package/public-api/services/solana.js.map +1 -1
  67. package/public-api/services/solana.mjs +0 -3
  68. package/public-api/services/solana.mjs.map +1 -1
  69. package/public-api/services/users.d.mts +23 -1
  70. package/public-api/services/users.d.mts.map +1 -1
  71. package/public-api/services/users.d.ts +23 -1
  72. package/public-api/services/users.d.ts.map +1 -1
  73. package/public-api/services/users.js +24 -0
  74. package/public-api/services/users.js.map +1 -1
  75. package/public-api/services/users.mjs +24 -0
  76. package/public-api/services/users.mjs.map +1 -1
  77. package/public-api/services/utils/auth.d.mts +6 -17
  78. package/public-api/services/utils/auth.d.mts.map +1 -1
  79. package/public-api/services/utils/auth.d.ts +6 -17
  80. package/public-api/services/utils/auth.d.ts.map +1 -1
  81. package/public-api/services/utils/auth.js +17 -69
  82. package/public-api/services/utils/auth.js.map +1 -1
  83. package/public-api/services/utils/auth.mjs +15 -66
  84. package/public-api/services/utils/auth.mjs.map +1 -1
  85. package/public-api/services/utils.d.mts +2 -1
  86. package/public-api/services/utils.d.mts.map +1 -1
  87. package/public-api/services/utils.d.ts +2 -1
  88. package/public-api/services/utils.d.ts.map +1 -1
  89. package/public-api/services/utils.js +2 -2
  90. package/public-api/services/utils.js.map +1 -1
  91. package/public-api/services/utils.mjs +2 -2
  92. package/public-api/services/utils.mjs.map +1 -1
  93. package/resources/policies.d.mts +2 -1
  94. package/resources/policies.d.mts.map +1 -1
  95. package/resources/policies.d.ts +2 -1
  96. package/resources/policies.d.ts.map +1 -1
  97. package/resources/users.d.mts +16 -5
  98. package/resources/users.d.mts.map +1 -1
  99. package/resources/users.d.ts +16 -5
  100. package/resources/users.d.ts.map +1 -1
  101. package/resources/users.js +2 -2
  102. package/resources/users.js.map +1 -1
  103. package/resources/users.mjs +2 -2
  104. package/resources/users.mjs.map +1 -1
  105. package/resources/wallets/wallets.d.mts +7 -22
  106. package/resources/wallets/wallets.d.mts.map +1 -1
  107. package/resources/wallets/wallets.d.ts +7 -22
  108. package/resources/wallets/wallets.d.ts.map +1 -1
  109. package/resources/wallets/wallets.js.map +1 -1
  110. package/resources/wallets/wallets.mjs.map +1 -1
  111. package/src/client.ts +2 -2
  112. package/src/index.ts +15 -1
  113. package/src/lib/auth.ts +210 -0
  114. package/src/lib/cryptography.ts +9 -1
  115. package/src/lib/identity-token.ts +280 -0
  116. package/src/lib/user-utils.ts +31 -0
  117. package/src/public-api/PrivyClient.ts +12 -2
  118. package/src/public-api/services/ethereum.ts +0 -8
  119. package/src/public-api/services/solana.ts +0 -4
  120. package/src/public-api/services/users.ts +36 -2
  121. package/src/public-api/services/utils/auth.ts +22 -86
  122. package/src/public-api/services/utils.ts +3 -2
  123. package/src/resources/policies.ts +2 -1
  124. package/src/resources/users.ts +27 -3
  125. package/src/resources/wallets/wallets.ts +14 -33
  126. package/src/version.ts +1 -1
  127. package/version.d.mts +1 -1
  128. package/version.d.mts.map +1 -1
  129. package/version.d.ts +1 -1
  130. package/version.d.ts.map +1 -1
  131. package/version.js +1 -1
  132. package/version.js.map +1 -1
  133. package/version.mjs +1 -1
  134. package/version.mjs.map +1 -1
@@ -0,0 +1,280 @@
1
+ import { JWTPayload } from 'jose';
2
+ import { User } from '../resources';
3
+ import { PrivyAPIError } from '../error';
4
+ import { EmbeddedWalletLinkedAccount, ExternalWalletLinkedAccount } from './user-utils';
5
+
6
+ /**
7
+ * Parses the payload of an identity token (JWT) into a `User` object.
8
+ * Note that the user object may be incomplete due to the size constraints of the identity token.
9
+ *
10
+ * @param payload The payload of the identity token.
11
+ * @returns The user object parsed from the identity token.
12
+ * @throws If the payload is invalid.
13
+ */
14
+ export function parseUserFromIdentityTokenPayload(payload: JWTPayload): User {
15
+ const customMetadata = parseCustomMetadataClaim(payload);
16
+
17
+ return {
18
+ id: payload.sub as string,
19
+ created_at: parseInt(payload['cr'] as string),
20
+ is_guest: payload['guest'] === 't',
21
+ linked_accounts: parseLinkedAccountsClaim(payload),
22
+ ...(customMetadata ? { custom_metadata: customMetadata } : {}),
23
+ has_accepted_terms: false,
24
+ mfa_methods: [],
25
+ };
26
+ }
27
+
28
+ function parseLinkedAccountsClaim(payload: JWTPayload): User['linked_accounts'] {
29
+ const linkedAccountsClaim = payload['linked_accounts'];
30
+ if (typeof linkedAccountsClaim !== 'string') {
31
+ throw new InvalidIdentityTokenError('Unable to parse identity token');
32
+ }
33
+ const parsedLinkedAccounts = JSON.parse(linkedAccountsClaim) as unknown;
34
+ if (!Array.isArray(parsedLinkedAccounts)) {
35
+ throw new InvalidIdentityTokenError('Unable to parse identity token');
36
+ }
37
+
38
+ return parsedLinkedAccounts
39
+ .map(mapIdLinkedAccountToUserLinkedAccount)
40
+ .filter((account) => account !== null);
41
+ }
42
+
43
+ function mapIdLinkedAccountToUserLinkedAccount(account: any): User['linked_accounts'][number] | null {
44
+ if (account.type === 'email') {
45
+ return {
46
+ type: 'email',
47
+ address: account.address,
48
+ first_verified_at: null,
49
+ verified_at: account.lv,
50
+ latest_verified_at: account.lv,
51
+ } satisfies User.LinkedAccountEmail;
52
+ }
53
+ if (account.type === 'phone') {
54
+ return {
55
+ type: 'phone',
56
+ phoneNumber: account.phone_number,
57
+ first_verified_at: null,
58
+ verified_at: account.lv,
59
+ latest_verified_at: account.lv,
60
+ } satisfies User.LinkedAccountPhone;
61
+ }
62
+
63
+ // Parses all wallet types
64
+ if (account.type === 'wallet') {
65
+ if (account.wallet_client_type === 'privy') {
66
+ return {
67
+ type: 'wallet',
68
+ wallet_client_type: 'privy',
69
+ wallet_client: 'privy',
70
+ connector_type: 'embedded',
71
+ id: account.id,
72
+ address: account.address,
73
+ chain_type: account.chain_type,
74
+ first_verified_at: null,
75
+ verified_at: account.lv,
76
+ latest_verified_at: account.lv,
77
+ // The following fields are not present in the identity token,
78
+ // but are required. We set them to some safe defaults.
79
+ chain_id: '',
80
+ delegated: false,
81
+ imported: false,
82
+ public_key: '',
83
+ wallet_index: 0,
84
+ recovery_method: account.id ? 'privy-v2' : 'privy',
85
+ } satisfies EmbeddedWalletLinkedAccount;
86
+ }
87
+ return {
88
+ type: 'wallet',
89
+ wallet_client: 'unknown',
90
+ address: account.address,
91
+ chain_type: account.chain_type,
92
+ first_verified_at: null,
93
+ verified_at: account.lv,
94
+ latest_verified_at: account.lv,
95
+ } satisfies ExternalWalletLinkedAccount;
96
+ }
97
+ if (account.type === 'smart_wallet') {
98
+ return {
99
+ type: 'smart_wallet',
100
+ address: account.address,
101
+ smart_wallet_type: account.smart_wallet_type,
102
+ first_verified_at: null,
103
+ verified_at: account.lv,
104
+ latest_verified_at: account.lv,
105
+ } satisfies User.LinkedAccountSmartWallet;
106
+ }
107
+ if (account.type === 'farcaster') {
108
+ return {
109
+ type: 'farcaster',
110
+ fid: account.fid,
111
+ username: account.username,
112
+ first_verified_at: null,
113
+ verified_at: account.lv,
114
+ latest_verified_at: account.lv,
115
+ owner_address: account.oa,
116
+ } satisfies User.LinkedAccountFarcaster;
117
+ }
118
+ if (account.type === 'google_oauth') {
119
+ return {
120
+ type: 'google_oauth',
121
+ subject: account.subject,
122
+ email: account.email,
123
+ name: account.name,
124
+ first_verified_at: null,
125
+ verified_at: account.lv,
126
+ latest_verified_at: account.lv,
127
+ } satisfies User.LinkedAccountGoogleOAuth;
128
+ }
129
+ if (account.type === 'twitter_oauth') {
130
+ // We send along three potential URL shapes here based on possible profile picture URLs, all
131
+ // done to preserve size.
132
+ // 1. pfp begins with `default`, in which case we use the default profile picture URL structure
133
+ // 2. pfp does not start with https, in which case we assume the profile picture URL structure
134
+ // 3. Otherwise, we use the pfp URL as-is
135
+ let pfp = account.pfp ? account.pfp : null;
136
+ if (pfp?.startsWith('default')) {
137
+ pfp = `https://abs.twimg.com/sticky/default_profile_images/${pfp}`;
138
+ } else if (!pfp?.startsWith('https://')) {
139
+ pfp = `https://pbs.twimg.com/profile_images/${pfp}`;
140
+ }
141
+
142
+ return {
143
+ type: 'twitter_oauth',
144
+ subject: account.subject,
145
+ username: account.username,
146
+ name: account.name ? account.name : null,
147
+ profile_picture_url: pfp,
148
+ first_verified_at: null,
149
+ verified_at: account.lv,
150
+ latest_verified_at: account.lv,
151
+ } satisfies User.LinkedAccountTwitterOAuth;
152
+ }
153
+ if (account.type === 'discord_oauth') {
154
+ return {
155
+ type: 'discord_oauth',
156
+ subject: account.subject,
157
+ username: account.username,
158
+ email: null, // not a part of the identity token
159
+ first_verified_at: null,
160
+ verified_at: account.lv,
161
+ latest_verified_at: account.lv,
162
+ } satisfies User.LinkedAccountDiscordOAuth;
163
+ }
164
+ if (account.type === 'github_oauth') {
165
+ return {
166
+ type: 'github_oauth',
167
+ subject: account.subject,
168
+ username: account.username,
169
+ email: null, // not a part of the identity token
170
+ name: null, // not a part of the identity token
171
+ first_verified_at: null,
172
+ verified_at: account.lv,
173
+ latest_verified_at: account.lv,
174
+ } satisfies User.LinkedAccountGitHubOAuth;
175
+ }
176
+ if (account.type === 'spotify_oauth') {
177
+ return {
178
+ type: 'spotify_oauth',
179
+ subject: account.subject,
180
+ email: null, // not a part of the identity token
181
+ name: null, // not a part of the identity token
182
+ first_verified_at: null,
183
+ verified_at: account.lv,
184
+ latest_verified_at: account.lv,
185
+ } satisfies User.LinkedAccountSpotifyOAuth;
186
+ }
187
+ if (account.type === 'instagram_oauth') {
188
+ return {
189
+ type: 'instagram_oauth',
190
+ subject: account.subject,
191
+ username: account.username,
192
+ first_verified_at: null,
193
+ verified_at: account.lv,
194
+ latest_verified_at: account.lv,
195
+ } satisfies User.LinkedAccountInstagramOAuth;
196
+ }
197
+ if (account.type === 'tiktok_oauth') {
198
+ return {
199
+ type: 'tiktok_oauth',
200
+ subject: account.subject,
201
+ username: account.username,
202
+ name: null, // not a part of the identity token
203
+ first_verified_at: null,
204
+ verified_at: account.lv,
205
+ latest_verified_at: account.lv,
206
+ } satisfies User.LinkedAccountTiktokOAuth;
207
+ }
208
+ if (account.type === 'linkedin_oauth') {
209
+ return {
210
+ type: 'linkedin_oauth',
211
+ subject: account.subject,
212
+ email: account.email,
213
+ first_verified_at: null,
214
+ verified_at: account.lv,
215
+ latest_verified_at: account.lv,
216
+ } satisfies User.LinkedAccountLinkedInOAuth;
217
+ }
218
+ if (account.type === 'apple_oauth') {
219
+ return {
220
+ type: 'apple_oauth',
221
+ subject: account.subject,
222
+ email: account.email,
223
+ first_verified_at: null,
224
+ verified_at: account.lv,
225
+ latest_verified_at: account.lv,
226
+ } satisfies User.LinkedAccountAppleOAuth;
227
+ }
228
+ if (account.type === 'cross_app') {
229
+ return {
230
+ type: 'cross_app',
231
+ subject: account.subject,
232
+ provider_app_id: account.provider_app_id,
233
+ embedded_wallets: account.embedded_wallets,
234
+ smart_wallets: account.smart_wallets,
235
+ first_verified_at: null,
236
+ verified_at: account.lv,
237
+ latest_verified_at: account.lv,
238
+ } satisfies User.LinkedAccountCrossApp;
239
+ }
240
+ if (account.type === 'custom_auth') {
241
+ return {
242
+ type: 'custom_auth',
243
+ custom_user_id: account.custom_user_id,
244
+ first_verified_at: null,
245
+ verified_at: account.lv,
246
+ latest_verified_at: account.lv,
247
+ } satisfies User.LinkedAccountCustomJwt;
248
+ }
249
+
250
+ if (account.type === 'telegram') {
251
+ return {
252
+ type: 'telegram',
253
+ telegram_user_id: account.telegram_user_id,
254
+ username: account.username,
255
+ first_verified_at: null,
256
+ verified_at: account.lv,
257
+ latest_verified_at: account.lv,
258
+ } satisfies User.LinkedAccountTelegram;
259
+ }
260
+
261
+ return null;
262
+ }
263
+
264
+ function parseCustomMetadataClaim(payload: JWTPayload): User['custom_metadata'] {
265
+ const customMetadataClaim = payload['custom_metadata'];
266
+ if (customMetadataClaim === undefined) {
267
+ return undefined;
268
+ }
269
+ if (typeof customMetadataClaim !== 'string') {
270
+ throw new InvalidIdentityTokenError('Unable to parse identity token');
271
+ }
272
+ const parsedCustomMetadata = JSON.parse(customMetadataClaim) as unknown;
273
+ if (!parsedCustomMetadata || typeof parsedCustomMetadata !== 'object') {
274
+ throw new InvalidIdentityTokenError('Unable to parse identity token');
275
+ }
276
+
277
+ return parsedCustomMetadata as User['custom_metadata'];
278
+ }
279
+
280
+ export class InvalidIdentityTokenError extends PrivyAPIError {}
@@ -0,0 +1,31 @@
1
+ import type { User } from '../resources/users';
2
+
3
+ export type LinkedAccount = User['linked_accounts'][number];
4
+
5
+ // prettier-ignore
6
+ export type ExternalWalletLinkedAccount = Exclude<
7
+ Extract<LinkedAccount, { type: 'wallet' }>,
8
+ EmbeddedWalletLinkedAccount
9
+ >;
10
+
11
+ /**
12
+ * An embedded wallet linked account is a wallet owned by the user that can be used with the Privy API.
13
+ */
14
+ export type EmbeddedWalletLinkedAccount = Extract<
15
+ LinkedAccount,
16
+ { type: 'wallet'; wallet_client_type: 'privy'; connector_type: 'embedded' }
17
+ >;
18
+
19
+ /**
20
+ * Determines if a given linked account is an embedded wallet account.
21
+ * Embedded wallet accounts are wallets that can be used with the Privy API.
22
+ *
23
+ * @param account a linked account
24
+ * @returns whether the account is an embedded wallet account
25
+ */
26
+ export const isEmbeddedWalletLinkedAccount = (
27
+ account: User['linked_accounts'][number],
28
+ ): account is EmbeddedWalletLinkedAccount =>
29
+ account.type === 'wallet' &&
30
+ account.wallet_client_type === 'privy' &&
31
+ account.connector_type === 'embedded';
@@ -7,6 +7,7 @@ import { PrivyUsersService } from './services/users';
7
7
  import { PrivyUtils } from './services/utils';
8
8
  import { JwtExchangeService } from '../lib/jwt-exchange';
9
9
  import { VERSION } from '../version';
10
+ import { createPrivyAppJWKS } from '../lib/auth';
10
11
 
11
12
  type InternalClientOptions = Omit<ClientOptions, 'appID' | 'appSecret' | 'baseUrl'>;
12
13
 
@@ -15,6 +16,7 @@ export interface PrivyClientOptions extends InternalClientOptions {
15
16
  appSecret: string;
16
17
  apiUrl?: string;
17
18
  authorizationKeyCacheMaxCapacity?: number;
19
+ jwtVerificationKey?: string;
18
20
  }
19
21
 
20
22
  const DEFAULT_AUTHORIZATION_KEY_CACHE_MAX_CAPACITY = 1000;
@@ -40,6 +42,7 @@ export class PrivyClient {
40
42
  apiUrl,
41
43
  authorizationKeyCacheMaxCapacity = DEFAULT_AUTHORIZATION_KEY_CACHE_MAX_CAPACITY,
42
44
  defaultHeaders,
45
+ jwtVerificationKey,
43
46
  ...clientOptions
44
47
  }: PrivyClientOptions) {
45
48
  this.privyApiClient = new PrivyAPI({
@@ -53,6 +56,13 @@ export class PrivyClient {
53
56
  'privy-client': `node:${VERSION}`,
54
57
  },
55
58
  });
59
+ const appJwks = createPrivyAppJWKS({
60
+ appId: this.privyApiClient.appID,
61
+ apiUrl: this.privyApiClient.baseURL,
62
+ headers: { 'privy-client': `node:${VERSION}` },
63
+ verificationKeyOverride: jwtVerificationKey,
64
+ });
65
+
56
66
  this.jwtExchangeService = new JwtExchangeService(
57
67
  this.privyApiClient.wallets,
58
68
  authorizationKeyCacheMaxCapacity,
@@ -61,8 +71,8 @@ export class PrivyClient {
61
71
  this.policiesService = new PrivyPoliciesService(this.privyApiClient, this);
62
72
  this.transactionsService = new PrivyTransactionsService(this.privyApiClient);
63
73
  this.keyQuorumsService = new PrivyKeyQuorumsService(this.privyApiClient, this);
64
- this.usersService = new PrivyUsersService(this.privyApiClient);
65
- this.utilsService = new PrivyUtils(this.privyApiClient, this);
74
+ this.usersService = new PrivyUsersService(this.privyApiClient, appJwks);
75
+ this.utilsService = new PrivyUtils(this.privyApiClient, this, appJwks);
66
76
  }
67
77
 
68
78
  public wallets(): PrivyWalletsService {
@@ -57,10 +57,6 @@ export class PrivyEthereumService {
57
57
  chain_type: 'ethereum',
58
58
  });
59
59
 
60
- if (!response.data) {
61
- throw new Error(response.error?.message ?? 'Unexpected response from Privy API');
62
- }
63
-
64
60
  return response.data;
65
61
  }
66
62
 
@@ -100,10 +96,6 @@ export class PrivyEthereumService {
100
96
  chain_type: 'ethereum',
101
97
  });
102
98
 
103
- if (!response.data) {
104
- throw new Error(response.error?.message ?? 'Unexpected response from Privy API');
105
- }
106
-
107
99
  return response.data;
108
100
  }
109
101
  }
@@ -75,10 +75,6 @@ export class PrivySolanaService {
75
75
  params,
76
76
  });
77
77
 
78
- if (!response.data) {
79
- throw new Error(response.error?.message ?? 'Unexpected response from Privy API');
80
- }
81
-
82
78
  return response.data;
83
79
  }
84
80
  }
@@ -1,3 +1,37 @@
1
- import { Users } from '../../resources';
1
+ import { PrivyAPI } from '../../client';
2
+ import { PrivyAppJWKS, verifyIdentityToken } from '../../lib/auth';
3
+ import { User, Users } from '../../resources';
2
4
 
3
- export class PrivyUsersService extends Users {}
5
+ export class PrivyUsersService extends Users {
6
+ private appJwks: PrivyAppJWKS;
7
+ constructor(privyApiClient: PrivyAPI, appJwks: PrivyAppJWKS) {
8
+ super(privyApiClient);
9
+ this.appJwks = appJwks;
10
+ }
11
+
12
+ /**
13
+ * Gets a user object from the identity token.
14
+ * This verifies the token and parses the payload into a `User` object.
15
+ * Note the user object may be incomplete due to the size constraints of the identity token.
16
+ *
17
+ * @param input.id_token the identity token to parse.
18
+ * @returns the user object parsed from the identity token.
19
+ *
20
+ * @example
21
+ * const idToken = req.cookies.get('privy-id-token'); // or however your framework surfaces cookies
22
+ * const user = await client.users().get({idToken});
23
+ */
24
+ public async get({ id_token }: PrivyUsersService.GetInput): Promise<User> {
25
+ return verifyIdentityToken({
26
+ identity_token: id_token,
27
+ app_id: this._client.appID,
28
+ verification_key: this.appJwks,
29
+ });
30
+ }
31
+ }
32
+
33
+ export namespace PrivyUsersService {
34
+ export type GetInput = {
35
+ id_token: string;
36
+ };
37
+ }
@@ -1,21 +1,18 @@
1
- import { createRemoteJWKSet, importSPKI, errors as joseErrors, jwtVerify, JWTVerifyResult } from 'jose';
2
1
  import { PrivyAPI } from '../../../client';
3
- import { PrivyAPIError } from '../../../core/error';
4
-
5
- const DEFAULT_JWKS_CACHE_MAX_AGE = 10 * 60 * 1000; // 10 minutes
6
-
7
- const JWT_ALGORITHM = 'ES256';
8
- const JWT_ISSUER = 'privy.io';
2
+ import {
3
+ PrivyAppJWKS,
4
+ verifyAuthToken,
5
+ VerifyAuthTokenResponse,
6
+ verifyIdentityToken,
7
+ } from '../../../lib/auth';
8
+ import { User } from '../../../resources';
9
9
 
10
10
  export class PrivyAuthUtils {
11
- private remoteJwks: ReturnType<typeof createRemoteJWKSet>;
11
+ private appJwks: PrivyAppJWKS;
12
12
  private privyAppID: string;
13
13
 
14
- constructor(privyApiClient: PrivyAPI) {
15
- this.remoteJwks = createRemoteJWKSet(
16
- new URL(`${privyApiClient.baseURL}/v1/apps/${privyApiClient.appID}/jwks.json`),
17
- { cacheMaxAge: DEFAULT_JWKS_CACHE_MAX_AGE },
18
- );
14
+ constructor(privyApiClient: PrivyAPI, appJwks: PrivyAppJWKS) {
15
+ this.appJwks = appJwks;
19
16
  this.privyAppID = privyApiClient.appID;
20
17
  }
21
18
 
@@ -23,83 +20,22 @@ export class PrivyAuthUtils {
23
20
  * Verifies the authentication token, and returns the payload if it is valid.
24
21
  *
25
22
  * @param authToken - The authentication token to verify.
26
- * @param verificationKeyOverride - The verification key to use instead of calling the JWKS endpoint.
27
23
  * @returns The payload of the token if it is valid.
28
24
  * @throws If the token is invalid.
29
25
  */
30
- public async verifyAuthToken(
31
- authToken: string,
32
- verificationKeyOverride?: string,
33
- ): Promise<PrivyUsersService.VerifyAuthTokenResponse> {
34
- // Because of a type difference, the calls cannot be merged into one.
35
- let verifiedToken: JWTVerifyResult;
36
- if (verificationKeyOverride !== undefined) {
37
- const verificationKey = await importSPKI(verificationKeyOverride, JWT_ALGORITHM);
38
- verifiedToken = await jwtVerify(authToken, verificationKey, {
39
- typ: 'JWT',
40
- algorithms: [JWT_ALGORITHM],
41
- issuer: JWT_ISSUER,
42
- audience: this.privyAppID,
43
- }).catch(mapAndThrowJoseErrors);
44
- } else {
45
- verifiedToken = await jwtVerify(authToken, this.remoteJwks, {
46
- typ: 'JWT',
47
- algorithms: [JWT_ALGORITHM],
48
- issuer: JWT_ISSUER,
49
- audience: this.privyAppID,
50
- }).catch(mapAndThrowJoseErrors);
51
- }
52
-
53
- return {
54
- appId: throwIfNotString(verifiedToken.payload.aud),
55
- issuer: throwIfNotString(verifiedToken.payload.iss),
56
- issuedAt: throwIfNotNumber(verifiedToken.payload.iat),
57
- expiration: throwIfNotNumber(verifiedToken.payload.exp),
58
- sessionId: throwIfNotString(verifiedToken.payload['sid']),
59
- userId: throwIfNotString(verifiedToken.payload.sub),
60
- };
26
+ public async verifyAuthToken(authToken: string): Promise<VerifyAuthTokenResponse> {
27
+ return verifyAuthToken({
28
+ auth_token: authToken,
29
+ app_id: this.privyAppID,
30
+ verification_key: this.appJwks,
31
+ });
61
32
  }
62
- }
63
-
64
- export namespace PrivyUsersService {
65
- export type VerifyAuthTokenResponse = {
66
- appId: string;
67
- issuer: string;
68
- issuedAt: number;
69
- expiration: number;
70
- sessionId: string;
71
- userId: string;
72
- };
73
- }
74
-
75
- export class InvalidAuthTokenError extends PrivyAPIError {}
76
-
77
- /** Used for asserting the values in the token payload are strings. */
78
- function throwIfNotString(value: unknown): string {
79
- if (!value || typeof value !== 'string') {
80
- throw new InvalidAuthTokenError("Token's payload is invalid");
81
- }
82
- return value;
83
- }
84
-
85
- /** Used for asserting the values in the token payload are numbers. */
86
- function throwIfNotNumber(value: unknown): number {
87
- if (!value || typeof value !== 'number') {
88
- throw new InvalidAuthTokenError("Token's payload is invalid");
89
- }
90
- return value;
91
- }
92
33
 
93
- /**
94
- * Used to catch errors thrown by async `jose` functions and map to our own error types.
95
- * This method will **always** throw an error, so it's return type is `never`.
96
- */
97
- function mapAndThrowJoseErrors(error: unknown): never {
98
- if (error instanceof joseErrors.JWTExpired) {
99
- throw new InvalidAuthTokenError('Authentication token expired');
100
- } else if (error instanceof joseErrors.JWTClaimValidationFailed || error instanceof joseErrors.JWTInvalid) {
101
- throw new InvalidAuthTokenError('Authentication token is invalid');
102
- } else {
103
- throw new InvalidAuthTokenError('Failed to verify authentication token');
34
+ public async verifyIdentityToken(identityToken: string): Promise<User> {
35
+ return verifyIdentityToken({
36
+ identity_token: identityToken,
37
+ app_id: this.privyAppID,
38
+ verification_key: this.appJwks,
39
+ });
104
40
  }
105
41
  }
@@ -1,3 +1,4 @@
1
+ import { PrivyAppJWKS } from '../../lib/auth';
1
2
  import { PrivyAPI } from '../../client';
2
3
  import { PrivyClient } from '../PrivyClient';
3
4
  import { PrivyAuthUtils } from './utils/auth';
@@ -9,10 +10,10 @@ export class PrivyUtils {
9
10
  private _requestFormatter: PrivyRequestFormatter;
10
11
  private _auth: PrivyAuthUtils;
11
12
 
12
- constructor(privyApiClient: PrivyAPI, privyClient: PrivyClient) {
13
+ constructor(privyApiClient: PrivyAPI, privyClient: PrivyClient, appJwks: PrivyAppJWKS) {
13
14
  this._requestSigner = new PrivyRequestSigner(privyClient);
14
15
  this._requestFormatter = new PrivyRequestFormatter();
15
- this._auth = new PrivyAuthUtils(privyApiClient);
16
+ this._auth = new PrivyAuthUtils(privyApiClient, appJwks);
16
17
  }
17
18
 
18
19
  public requestSigner(): PrivyRequestSigner {
@@ -290,7 +290,8 @@ export interface Policy {
290
290
 
291
291
  export namespace Policy {
292
292
  /**
293
- * The rules that apply to each method the policy covers.
293
+ * A rule that defines the conditions and action to take if the conditions are
294
+ * true.
294
295
  */
295
296
  export interface Rule {
296
297
  id: string;
@@ -63,10 +63,10 @@ export class Users extends APIResource {
63
63
  *
64
64
  * @example
65
65
  * ```ts
66
- * const user = await client.users.get('user_id');
66
+ * const user = await client.users._get('user_id');
67
67
  * ```
68
68
  */
69
- get(userID: string, options?: RequestOptions): APIPromise<User> {
69
+ _get(userID: string, options?: RequestOptions): APIPromise<User> {
70
70
  return this._client.get(path`/v1/users/${userID}`, options);
71
71
  }
72
72
 
@@ -352,6 +352,7 @@ export interface User {
352
352
  | User.LinkedAccountSmartWallet
353
353
  | User.LinkedAccountPasskey
354
354
  | User.LinkedAccountFarcaster
355
+ | User.LinkedAccountTelegram
355
356
  | User.LinkedAccountEthereum
356
357
  | User.LinkedAccountEthereumEmbeddedWallet
357
358
  | User.LinkedAccountSolana
@@ -685,6 +686,26 @@ export namespace User {
685
686
  username?: string;
686
687
  }
687
688
 
689
+ export interface LinkedAccountTelegram {
690
+ first_verified_at: number | null;
691
+
692
+ latest_verified_at: number | null;
693
+
694
+ telegram_user_id: string;
695
+
696
+ type: 'telegram';
697
+
698
+ verified_at: number;
699
+
700
+ first_name?: string | null;
701
+
702
+ last_name?: string | null;
703
+
704
+ photo_url?: string | null;
705
+
706
+ username?: string | null;
707
+ }
708
+
688
709
  export interface LinkedAccountEthereum {
689
710
  address: string;
690
711
 
@@ -1113,7 +1134,8 @@ export namespace UserCreateParams {
1113
1134
  | 'near'
1114
1135
  | 'spark'
1115
1136
  | 'ton'
1116
- | 'starknet';
1137
+ | 'starknet'
1138
+ | 'movement';
1117
1139
 
1118
1140
  /**
1119
1141
  * Additional signers for the wallet.
@@ -1210,6 +1232,8 @@ export namespace UserPregenerateWalletsParams {
1210
1232
  | 'cosmos'
1211
1233
  | 'stellar'
1212
1234
  | 'sui'
1235
+ | 'aptos'
1236
+ | 'movement'
1213
1237
  | 'tron'
1214
1238
  | 'bitcoin-segwit'
1215
1239
  | 'near'