@sentroy-co/client-sdk 2.13.8 → 2.14.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 (59) hide show
  1. package/README.md +24 -5
  2. package/dist/auth/admin/index.d.ts +111 -10
  3. package/dist/auth/admin/index.d.ts.map +1 -1
  4. package/dist/auth/admin/index.js +125 -20
  5. package/dist/auth/admin/index.js.map +1 -1
  6. package/dist/auth/client.d.ts +127 -1
  7. package/dist/auth/client.d.ts.map +1 -1
  8. package/dist/auth/client.js +361 -3
  9. package/dist/auth/client.js.map +1 -1
  10. package/dist/auth/index.d.ts +1 -1
  11. package/dist/auth/index.d.ts.map +1 -1
  12. package/dist/auth/index.js.map +1 -1
  13. package/dist/auth/react/index.d.ts +63 -4
  14. package/dist/auth/react/index.d.ts.map +1 -1
  15. package/dist/auth/react/index.js +180 -1
  16. package/dist/auth/react/index.js.map +1 -1
  17. package/dist/auth/types.d.ts +55 -0
  18. package/dist/auth/types.d.ts.map +1 -1
  19. package/dist/cli/ai.d.ts +35 -0
  20. package/dist/cli/ai.d.ts.map +1 -0
  21. package/dist/cli/ai.js +399 -0
  22. package/dist/cli/ai.js.map +1 -0
  23. package/dist/cli/args.d.ts +62 -0
  24. package/dist/cli/args.d.ts.map +1 -0
  25. package/dist/cli/args.js +199 -0
  26. package/dist/cli/args.js.map +1 -0
  27. package/dist/cli/env.d.ts.map +1 -1
  28. package/dist/cli/env.js +8 -2
  29. package/dist/cli/env.js.map +1 -1
  30. package/dist/cli/format.d.ts +37 -0
  31. package/dist/cli/format.d.ts.map +1 -0
  32. package/dist/cli/format.js +129 -0
  33. package/dist/cli/format.js.map +1 -0
  34. package/dist/cli/index.d.ts +8 -2
  35. package/dist/cli/index.d.ts.map +1 -1
  36. package/dist/cli/index.js +128 -25
  37. package/dist/cli/index.js.map +1 -1
  38. package/dist/cli/mail.d.ts +25 -0
  39. package/dist/cli/mail.d.ts.map +1 -0
  40. package/dist/cli/mail.js +253 -0
  41. package/dist/cli/mail.js.map +1 -0
  42. package/dist/cli/storage.d.ts +28 -0
  43. package/dist/cli/storage.d.ts.map +1 -0
  44. package/dist/cli/storage.js +189 -0
  45. package/dist/cli/storage.js.map +1 -0
  46. package/package.json +8 -2
  47. package/skill/SKILL.md +542 -0
  48. package/src/auth/admin/index.ts +227 -31
  49. package/src/auth/client.ts +438 -4
  50. package/src/auth/index.ts +9 -0
  51. package/src/auth/react/index.tsx +255 -4
  52. package/src/auth/types.ts +66 -0
  53. package/src/cli/ai.ts +440 -0
  54. package/src/cli/args.ts +225 -0
  55. package/src/cli/env.ts +10 -2
  56. package/src/cli/format.ts +147 -0
  57. package/src/cli/index.ts +147 -25
  58. package/src/cli/mail.ts +363 -0
  59. package/src/cli/storage.ts +307 -0
@@ -1,34 +1,220 @@
1
1
  import { AuthHttp } from "../http"
2
- import type { SentroyAuthUser } from "../types"
2
+ import type {
3
+ SentroyAuthUser,
4
+ SignupResponse,
5
+ LoginResponse,
6
+ LoginOutcome,
7
+ AuthTokensResponse,
8
+ MfaChallengeResponse,
9
+ } from "../types"
3
10
 
4
11
  /**
5
12
  * Server-side Sentroy Auth admin SDK. **Node only — apiKey browser'a
6
- * koymayın**; bu sınıf Project'in master `aps_` token'ını taşır ve
7
- * Sentroy üzerindeki user pool'a yetki vermez.
13
+ * koymayın**; bu sınıf Project'in master `aps_` token'ını taşır.
8
14
  *
9
- * Tipik kullanım: backend, kendi `/api/auth/...` proxy'sinde RP-spesifik
10
- * authorization yapar, sonra `admin.users.get(...)` ile Sentroy'dan
11
- * end-user'ı çeker. JWT verify de bu SDK üzerinden — tüm akış stateless.
15
+ * Tüm public auth endpoint'lerini apiKey'le proxy eder ve JWT'yi local
16
+ * verify edebilir (JWKS cache + RSA Subtle). RP backend tipik akış:
17
+ *
18
+ * const admin = new SentroyAuthAdmin({ projectSlug, apiKey })
19
+ * const out = await admin.users.signIn({ email, password })
20
+ * if (out.kind === "tokens") setCookie(out.data.accessToken, ...)
21
+ * else // MFA flow
22
+ *
23
+ * // Mid-request: verify
24
+ * const claims = await admin.verifyIdToken(req.cookies.accessToken)
25
+ *
26
+ * Token persistence yok — server-side request-scoped; caller cookie /
27
+ * session / DB nereye isterse oraya yazar.
12
28
  */
13
29
 
14
30
  export interface SentroyAuthAdminOptions {
15
31
  authBaseUrl?: string
16
32
  projectSlug: string
17
33
  apiKey: string
34
+ /** JWKS cache TTL (saniye). Default 3600 (1 saat) — JWT rotation
35
+ * grace period'una uyumlu; daha agresif rotation yapan project'ler
36
+ * daha düşük set edebilir. */
37
+ jwksCacheTtl?: number
38
+ }
39
+
40
+ interface CachedJwks {
41
+ keys: Record<string, unknown>[]
42
+ expiresAt: number
18
43
  }
19
44
 
20
45
  export class SentroyAuthAdmin {
21
46
  private readonly http: AuthHttp
22
- private cachedJwks: { keys: Record<string, unknown>[] } | null = null
47
+ private readonly jwksCacheTtl: number
48
+ private cachedJwks: CachedJwks | null = null
23
49
 
24
50
  constructor(opts: SentroyAuthAdminOptions) {
25
51
  this.http = new AuthHttp(opts)
52
+ this.jwksCacheTtl = opts.jwksCacheTtl ?? 3600
53
+ }
54
+
55
+ get projectSlug(): string {
56
+ return this.http.projectSlug
57
+ }
58
+
59
+ get baseUrl(): string {
60
+ return this.http.baseUrl
26
61
  }
27
62
 
28
63
  // ─── User pool admin ──────────────────────────────────────────────────
29
64
 
30
65
  users = {
31
- list: (opts: {
66
+ /**
67
+ * Server-side signup proxy. apiKey backend'de — browser'a sızmaz.
68
+ * Email verification project config'ine bağlı: required ise
69
+ * `emailVerificationRequired: true` döner, tokens undefined.
70
+ */
71
+ create: (input: {
72
+ email: string
73
+ password: string
74
+ displayName?: string
75
+ metadata?: Record<string, unknown>
76
+ }): Promise<SignupResponse> =>
77
+ this.http.request<SignupResponse>("/signup", {
78
+ method: "POST",
79
+ json: input,
80
+ }),
81
+
82
+ /**
83
+ * Server-side login proxy. MFA-aware: tokens VEYA MFA challenge.
84
+ * RP backend `out.kind === "mfa"` ise kullanıcıdan code alıp
85
+ * `users.verifyMfa(...)` çağırır.
86
+ */
87
+ signIn: async (input: {
88
+ email: string
89
+ password: string
90
+ rememberMe?: boolean
91
+ }): Promise<LoginOutcome> => {
92
+ const res = await this.http.request<
93
+ LoginResponse | MfaChallengeResponse
94
+ >("/login", { method: "POST", json: input })
95
+ if ("mfaRequired" in res && res.mfaRequired) {
96
+ return { kind: "mfa", data: res }
97
+ }
98
+ return { kind: "tokens", data: res as LoginResponse }
99
+ },
100
+
101
+ /** MFA verify ikinci adımı — `signIn` kind:"mfa" döndüyse. */
102
+ verifyMfa: (input: {
103
+ mfaToken: string
104
+ code?: string
105
+ recoveryCode?: string
106
+ }): Promise<LoginResponse> =>
107
+ this.http.request<LoginResponse>("/login/mfa/verify", {
108
+ method: "POST",
109
+ json: input,
110
+ }),
111
+
112
+ /** Refresh access token (rotation). Yeni refresh + access döner. */
113
+ refresh: (refreshToken: string): Promise<AuthTokensResponse> =>
114
+ this.http.request<AuthTokensResponse>("/refresh", {
115
+ method: "POST",
116
+ json: { refreshToken },
117
+ }),
118
+
119
+ /** Logout (refresh token revoke). */
120
+ signOut: (refreshToken: string): Promise<void> =>
121
+ this.http
122
+ .request<void>("/logout", {
123
+ method: "POST",
124
+ json: { refreshToken },
125
+ })
126
+ .then(() => undefined),
127
+
128
+ /** Verify email — link'ten gelen token. */
129
+ verifyEmail: (token: string): Promise<{ user: SentroyAuthUser }> =>
130
+ this.http.request<{ user: SentroyAuthUser }>("/verify-email", {
131
+ method: "POST",
132
+ json: { token },
133
+ }),
134
+
135
+ /** Password reset mail tetikle. */
136
+ requestPasswordReset: (email: string): Promise<void> =>
137
+ this.http
138
+ .request<void>("/password-reset/request", {
139
+ method: "POST",
140
+ json: { email },
141
+ })
142
+ .then(() => undefined),
143
+
144
+ /** Reset token + yeni şifre ile finalize. */
145
+ confirmPasswordReset: (input: {
146
+ token: string
147
+ newPassword: string
148
+ }): Promise<{ user: SentroyAuthUser }> =>
149
+ this.http.request<{ user: SentroyAuthUser }>("/password-reset/confirm", {
150
+ method: "POST",
151
+ json: input,
152
+ }),
153
+
154
+ /** Magic-link mail tetikle. */
155
+ sendMagicLink: (input: {
156
+ email: string
157
+ redirectUri?: string
158
+ }): Promise<void> =>
159
+ this.http
160
+ .request<void>("/magic-link/request", {
161
+ method: "POST",
162
+ json: input,
163
+ })
164
+ .then(() => undefined),
165
+
166
+ /** Magic-link token consume → login. */
167
+ consumeMagicLink: (token: string): Promise<LoginResponse> =>
168
+ this.http.request<LoginResponse>("/magic-link/consume", {
169
+ method: "POST",
170
+ json: { token },
171
+ }),
172
+
173
+ /** Davet token'ı ile yeni hesap + login. */
174
+ acceptInvitation: (input: {
175
+ token: string
176
+ password: string
177
+ displayName?: string
178
+ }): Promise<LoginResponse> =>
179
+ this.http.request<LoginResponse>("/invitation/accept", {
180
+ method: "POST",
181
+ json: input,
182
+ }),
183
+
184
+ /**
185
+ * Access token ile remote /me — JWT'ye bağımlı kalmadan canlı
186
+ * profile çek. Token expire ise SentroyAuthError fırlatır.
187
+ */
188
+ getUser: (accessToken: string): Promise<SentroyAuthUser> =>
189
+ this.http.request<SentroyAuthUser>("/me", {
190
+ method: "GET",
191
+ bearer: accessToken,
192
+ }),
193
+
194
+ /**
195
+ * Token bazlı userinfo (OIDC tarzı). `/userinfo` claims response —
196
+ * SentroyAuthUser shape'inden daha minimal olabilir.
197
+ */
198
+ getUserinfo: (
199
+ accessToken: string,
200
+ ): Promise<{
201
+ sub: string
202
+ email?: string
203
+ email_verified?: boolean
204
+ name?: string
205
+ picture?: string
206
+ }> =>
207
+ this.http.request("/userinfo", {
208
+ method: "GET",
209
+ bearer: accessToken,
210
+ }),
211
+
212
+ /**
213
+ * Admin list (paginated). **Şu an public API yok** — dashboard
214
+ * cookie-auth `/api/companies/[slug]/auth-projects/[id]/users`
215
+ * kullan. v2'de stk_ token'lı admin endpoint açılacak.
216
+ */
217
+ list: (_opts: {
32
218
  limit?: number
33
219
  skip?: number
34
220
  emailVerified?: boolean
@@ -39,18 +225,19 @@ export class SentroyAuthAdmin {
39
225
  throw new Error(
40
226
  "admin.users.list requires session-authenticated admin API; use dashboard /api/companies/[slug]/auth-projects/[id]/users instead. (v2 admin SDK will proxy this with stk_ tokens.)",
41
227
  )
42
- // NOTE Phase 5+: SDK admin endpoint'leri public path'lere taşınmadı;
43
- // şu an `/api/companies/...` cookie-auth ile. v2'de `/api/v1/admin/...`
44
- // RP token'ı ile authenticate eden ayrı public admin layer eklenir.
45
228
  },
46
229
  }
47
230
 
48
231
  // ─── ID token verification ─────────────────────────────────────────────
49
232
 
50
233
  /**
51
- * Local verify — JWKS cache'lenir (5dk TTL), JWT signature kontrolü
52
- * RS256 ile RP backend'inde stateless yapılır. `iss`/`aud` claim
53
- * eşleşmesi de kontrol edilir.
234
+ * Local verify — JWKS cache'lenir (default 1h TTL, opts ile değişir),
235
+ * JWT signature RS256 ile WebCrypto Subtle üzerinden verify edilir.
236
+ * `iss`/`aud` claim eşleşmesi de kontrol edilir.
237
+ *
238
+ * Throw'lar: malformed JWT, expired, iss/aud mismatch, key not found,
239
+ * signature mismatch. Tipik kullanım `try/catch` içinde — fail ise
240
+ * 401 dön.
54
241
  */
55
242
  async verifyIdToken(token: string): Promise<{
56
243
  sub: string
@@ -62,6 +249,7 @@ export class SentroyAuthAdmin {
62
249
  aud: string
63
250
  iat: number
64
251
  exp: number
252
+ [claim: string]: unknown
65
253
  }> {
66
254
  const parts = token.split(".")
67
255
  if (parts.length !== 3) {
@@ -83,12 +271,14 @@ export class SentroyAuthAdmin {
83
271
  if (typeof claims.exp !== "number" || claims.exp * 1000 < Date.now()) {
84
272
  throw new Error("Token expired.")
85
273
  }
86
- // iss + aud check
87
274
  const expectedIssSuffix = `/p/${this.http.projectSlug}`
88
- if (typeof claims.iss !== "string" || !claims.iss.endsWith(expectedIssSuffix)) {
275
+ if (
276
+ typeof claims.iss !== "string" ||
277
+ !claims.iss.endsWith(expectedIssSuffix)
278
+ ) {
89
279
  throw new Error("Issuer mismatch.")
90
280
  }
91
- // aud == project apiKeyPrefix (12 chars). API key first 12 = aud check.
281
+ // aud == project apiKeyPrefix (first 12 chars of api key)
92
282
  if (
93
283
  typeof claims.aud !== "string" ||
94
284
  !this.http.apiKey?.startsWith(claims.aud)
@@ -111,17 +301,22 @@ export class SentroyAuthAdmin {
111
301
  return claims as never
112
302
  }
113
303
 
304
+ /** JWKS cache'ini elle temizle (key rotation sonrası). */
305
+ invalidateJwksCache(): void {
306
+ this.cachedJwks = null
307
+ }
308
+
114
309
  private async fetchJwks(): Promise<{ keys: Record<string, unknown>[] }> {
115
- if (this.cachedJwks) return this.cachedJwks
116
- const jwks = await this.http.request<{ keys: Record<string, unknown>[] }>(
117
- "/jwks.json",
118
- { method: "GET" },
119
- )
120
- this.cachedJwks = jwks
121
- // 5dk cache — basit setTimeout invalidation
122
- setTimeout(() => {
123
- this.cachedJwks = null
124
- }, 5 * 60 * 1000)
310
+ if (this.cachedJwks && this.cachedJwks.expiresAt > Date.now()) {
311
+ return { keys: this.cachedJwks.keys }
312
+ }
313
+ const jwks = await this.http.request<{
314
+ keys: Record<string, unknown>[]
315
+ }>("/jwks.json", { method: "GET" })
316
+ this.cachedJwks = {
317
+ keys: jwks.keys,
318
+ expiresAt: Date.now() + this.jwksCacheTtl * 1000,
319
+ }
125
320
  return jwks
126
321
  }
127
322
  }
@@ -167,7 +362,9 @@ async function verifyRsaSignature(input: {
167
362
  const subtle =
168
363
  typeof crypto !== "undefined" && crypto.subtle ? crypto.subtle : null
169
364
  if (!subtle) {
170
- throw new Error("Web Crypto unavailable — upgrade Node >= 18 or run in a browser.")
365
+ throw new Error(
366
+ "Web Crypto unavailable — upgrade Node >= 18 or run in a browser.",
367
+ )
171
368
  }
172
369
  const key = await subtle.importKey(
173
370
  "jwk",
@@ -176,9 +373,8 @@ async function verifyRsaSignature(input: {
176
373
  false,
177
374
  ["verify"],
178
375
  )
179
- // Web Crypto types want ArrayBuffer-backed BufferSource. TypeScript
180
- // can't prove Uint8Array isn't SharedArrayBuffer-backed (DOM lib edge);
181
- // bytes are created fresh from base64 decode so ArrayBuffer-safe — cast.
376
+ // Web Crypto types want ArrayBuffer-backed BufferSource. Bytes are
377
+ // created fresh from base64 decode so they are ArrayBuffer-safe.
182
378
  const sigBytes = base64UrlToBytes(input.sigB64) as Uint8Array
183
379
  const dataBytes = new TextEncoder().encode(input.data) as Uint8Array
184
380
  const ok = await subtle.verify(