@sentroy-co/client-sdk 2.13.8 → 2.13.9
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/dist/auth/admin/index.d.ts +111 -10
- package/dist/auth/admin/index.d.ts.map +1 -1
- package/dist/auth/admin/index.js +125 -20
- package/dist/auth/admin/index.js.map +1 -1
- package/dist/auth/client.d.ts +127 -1
- package/dist/auth/client.d.ts.map +1 -1
- package/dist/auth/client.js +361 -3
- package/dist/auth/client.js.map +1 -1
- package/dist/auth/index.d.ts +1 -1
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/react/index.d.ts +63 -4
- package/dist/auth/react/index.d.ts.map +1 -1
- package/dist/auth/react/index.js +180 -1
- package/dist/auth/react/index.js.map +1 -1
- package/dist/auth/types.d.ts +55 -0
- package/dist/auth/types.d.ts.map +1 -1
- package/package.json +5 -1
- package/src/auth/admin/index.ts +227 -31
- package/src/auth/client.ts +438 -4
- package/src/auth/index.ts +9 -0
- package/src/auth/react/index.tsx +255 -4
- package/src/auth/types.ts +66 -0
package/src/auth/react/index.tsx
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
createContext,
|
|
5
|
+
useCallback,
|
|
5
6
|
useContext,
|
|
6
7
|
useEffect,
|
|
7
8
|
useMemo,
|
|
@@ -9,7 +10,13 @@ import {
|
|
|
9
10
|
type ReactNode,
|
|
10
11
|
} from "react"
|
|
11
12
|
import { SentroyAuth, type SentroyAuthOptions } from "../client"
|
|
12
|
-
import type {
|
|
13
|
+
import type {
|
|
14
|
+
SentroyAuthUser,
|
|
15
|
+
SessionSummary,
|
|
16
|
+
ActivityEntry,
|
|
17
|
+
MfaStatus,
|
|
18
|
+
PasskeySummary,
|
|
19
|
+
} from "../types"
|
|
13
20
|
|
|
14
21
|
/**
|
|
15
22
|
* Sentroy Auth React integration.
|
|
@@ -23,29 +30,46 @@ import type { SentroyAuthUser } from "../types"
|
|
|
23
30
|
* Provider içeride tek bir `SentroyAuth` instance tutar (mount/unmount
|
|
24
31
|
* arasında stable), `onAuthStateChanged` ile React state'i senkron tutar.
|
|
25
32
|
* `loading` ilk render → restore tamam mı henüz değil ayrımı için.
|
|
33
|
+
*
|
|
34
|
+
* Yeni method'lar (MFA, magic link, passkey, social, /me/*) tüm
|
|
35
|
+
* `auth` instance üzerinden erişilebilir — context'te kısayol olarak
|
|
36
|
+
* en sık kullanılanlar mevcut.
|
|
26
37
|
*/
|
|
27
38
|
|
|
28
39
|
interface AuthContextValue {
|
|
40
|
+
/** Underlying SDK instance — gelişmiş senaryolarda doğrudan kullan. */
|
|
29
41
|
auth: SentroyAuth
|
|
30
42
|
user: SentroyAuthUser | null
|
|
31
43
|
/** True iken provider ilk state'i restore etmiş değil — UI'da
|
|
32
44
|
* "spinner" göster, "redirect to /login" tetikleme. */
|
|
33
45
|
loading: boolean
|
|
34
|
-
|
|
35
|
-
* `signIn(...)` kullanabilir. */
|
|
46
|
+
// Sık kullanılan kısayollar (proxies)
|
|
36
47
|
signIn: SentroyAuth["signIn"]
|
|
37
48
|
signUp: SentroyAuth["signUp"]
|
|
38
49
|
signOut: SentroyAuth["signOut"]
|
|
39
50
|
sendPasswordReset: SentroyAuth["sendPasswordReset"]
|
|
40
51
|
verifyEmail: SentroyAuth["verifyEmail"]
|
|
52
|
+
verifyMfa: SentroyAuth["verifyMfa"]
|
|
53
|
+
sendMagicLink: SentroyAuth["sendMagicLink"]
|
|
54
|
+
consumeMagicLink: SentroyAuth["consumeMagicLink"]
|
|
55
|
+
acceptInvitation: SentroyAuth["acceptInvitation"]
|
|
56
|
+
socialAuthorizeUrl: SentroyAuth["socialAuthorizeUrl"]
|
|
57
|
+
consumeRedirectFragment: SentroyAuth["consumeRedirectFragment"]
|
|
41
58
|
}
|
|
42
59
|
|
|
43
60
|
const AuthContext = createContext<AuthContextValue | null>(null)
|
|
44
61
|
|
|
45
62
|
export function SentroyAuthProvider({
|
|
46
63
|
children,
|
|
64
|
+
autoConsumeFragment = true,
|
|
47
65
|
...opts
|
|
48
|
-
}: SentroyAuthOptions & {
|
|
66
|
+
}: SentroyAuthOptions & {
|
|
67
|
+
children: ReactNode
|
|
68
|
+
/** Mount'ta `window.location.hash`'ten social-login fragment'ı
|
|
69
|
+
* consume et — default true. RP'nin redirectUri'sinde session
|
|
70
|
+
* otomatik kurulur. */
|
|
71
|
+
autoConsumeFragment?: boolean
|
|
72
|
+
}) {
|
|
49
73
|
// Single instance — opts deep-compare'a girersek dependency drift'i
|
|
50
74
|
// restart'a yol açar. Caller `projectSlug` değiştirmemeli runtime'da.
|
|
51
75
|
const auth = useMemo(
|
|
@@ -64,6 +88,17 @@ export function SentroyAuthProvider({
|
|
|
64
88
|
return unsubscribe
|
|
65
89
|
}, [auth])
|
|
66
90
|
|
|
91
|
+
// Social login redirect handler — fragment varsa otomatik consume
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
if (!autoConsumeFragment) return
|
|
94
|
+
if (typeof window === "undefined") return
|
|
95
|
+
if (!window.location.hash.includes("access_token=")) return
|
|
96
|
+
auth.consumeRedirectFragment().catch(() => {
|
|
97
|
+
// Fragment varsa ama consume fail ise sessizce yut — caller
|
|
98
|
+
// gerekirse manuel tekrar dener.
|
|
99
|
+
})
|
|
100
|
+
}, [auth, autoConsumeFragment])
|
|
101
|
+
|
|
67
102
|
const value = useMemo<AuthContextValue>(
|
|
68
103
|
() => ({
|
|
69
104
|
auth,
|
|
@@ -74,6 +109,12 @@ export function SentroyAuthProvider({
|
|
|
74
109
|
signOut: () => auth.signOut(),
|
|
75
110
|
sendPasswordReset: (e) => auth.sendPasswordReset(e),
|
|
76
111
|
verifyEmail: (t) => auth.verifyEmail(t),
|
|
112
|
+
verifyMfa: (i) => auth.verifyMfa(i),
|
|
113
|
+
sendMagicLink: (i) => auth.sendMagicLink(i),
|
|
114
|
+
consumeMagicLink: (t) => auth.consumeMagicLink(t),
|
|
115
|
+
acceptInvitation: (i) => auth.acceptInvitation(i),
|
|
116
|
+
socialAuthorizeUrl: (p, o) => auth.socialAuthorizeUrl(p, o),
|
|
117
|
+
consumeRedirectFragment: () => auth.consumeRedirectFragment(),
|
|
77
118
|
}),
|
|
78
119
|
[auth, user, loading],
|
|
79
120
|
)
|
|
@@ -98,3 +139,213 @@ export function useAuth(): AuthContextValue {
|
|
|
98
139
|
export function useUser(): SentroyAuthUser | null {
|
|
99
140
|
return useAuth().user
|
|
100
141
|
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Reactive sessions hook — `/me/sessions` çağırır, refresh trigger eden
|
|
145
|
+
* `refresh()` döner. Manual revoke sonrası caller `refresh()` çağırır.
|
|
146
|
+
*/
|
|
147
|
+
export function useSessions(): {
|
|
148
|
+
sessions: SessionSummary[] | null
|
|
149
|
+
loading: boolean
|
|
150
|
+
error: Error | null
|
|
151
|
+
refresh: () => Promise<void>
|
|
152
|
+
revoke: (id: string) => Promise<void>
|
|
153
|
+
} {
|
|
154
|
+
const { auth, user } = useAuth()
|
|
155
|
+
const [sessions, setSessions] = useState<SessionSummary[] | null>(null)
|
|
156
|
+
const [loading, setLoading] = useState(false)
|
|
157
|
+
const [error, setError] = useState<Error | null>(null)
|
|
158
|
+
|
|
159
|
+
const refresh = useCallback(async () => {
|
|
160
|
+
if (!user) {
|
|
161
|
+
setSessions(null)
|
|
162
|
+
return
|
|
163
|
+
}
|
|
164
|
+
setLoading(true)
|
|
165
|
+
setError(null)
|
|
166
|
+
try {
|
|
167
|
+
const data = await auth.listSessions()
|
|
168
|
+
setSessions(data)
|
|
169
|
+
} catch (e) {
|
|
170
|
+
setError(e instanceof Error ? e : new Error(String(e)))
|
|
171
|
+
} finally {
|
|
172
|
+
setLoading(false)
|
|
173
|
+
}
|
|
174
|
+
}, [auth, user])
|
|
175
|
+
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
refresh()
|
|
178
|
+
}, [refresh])
|
|
179
|
+
|
|
180
|
+
const revoke = useCallback(
|
|
181
|
+
async (id: string) => {
|
|
182
|
+
await auth.revokeSession(id)
|
|
183
|
+
await refresh()
|
|
184
|
+
},
|
|
185
|
+
[auth, refresh],
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
return { sessions, loading, error, refresh, revoke }
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Reactive activity log hook — `/me/activity`. RP'nin "recent activity"
|
|
193
|
+
* tab'ı için.
|
|
194
|
+
*/
|
|
195
|
+
export function useActivity(): {
|
|
196
|
+
activity: ActivityEntry[] | null
|
|
197
|
+
loading: boolean
|
|
198
|
+
error: Error | null
|
|
199
|
+
refresh: () => Promise<void>
|
|
200
|
+
} {
|
|
201
|
+
const { auth, user } = useAuth()
|
|
202
|
+
const [activity, setActivity] = useState<ActivityEntry[] | null>(null)
|
|
203
|
+
const [loading, setLoading] = useState(false)
|
|
204
|
+
const [error, setError] = useState<Error | null>(null)
|
|
205
|
+
|
|
206
|
+
const refresh = useCallback(async () => {
|
|
207
|
+
if (!user) {
|
|
208
|
+
setActivity(null)
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
setLoading(true)
|
|
212
|
+
setError(null)
|
|
213
|
+
try {
|
|
214
|
+
const data = await auth.getActivity()
|
|
215
|
+
setActivity(data)
|
|
216
|
+
} catch (e) {
|
|
217
|
+
setError(e instanceof Error ? e : new Error(String(e)))
|
|
218
|
+
} finally {
|
|
219
|
+
setLoading(false)
|
|
220
|
+
}
|
|
221
|
+
}, [auth, user])
|
|
222
|
+
|
|
223
|
+
useEffect(() => {
|
|
224
|
+
refresh()
|
|
225
|
+
}, [refresh])
|
|
226
|
+
|
|
227
|
+
return { activity, loading, error, refresh }
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Reactive MFA status. enroll / verify / disable wrapper'ları, status
|
|
232
|
+
* otomatik yeniden çekilir.
|
|
233
|
+
*/
|
|
234
|
+
export function useMfa(): {
|
|
235
|
+
status: MfaStatus | null
|
|
236
|
+
loading: boolean
|
|
237
|
+
error: Error | null
|
|
238
|
+
refresh: () => Promise<void>
|
|
239
|
+
enrollTotp: SentroyAuth["mfa"]["enrollTotp"]
|
|
240
|
+
verifyTotpEnrollment: (code: string) => Promise<void>
|
|
241
|
+
disableTotp: (currentPassword: string) => Promise<void>
|
|
242
|
+
} {
|
|
243
|
+
const { auth, user } = useAuth()
|
|
244
|
+
const [status, setStatus] = useState<MfaStatus | null>(null)
|
|
245
|
+
const [loading, setLoading] = useState(false)
|
|
246
|
+
const [error, setError] = useState<Error | null>(null)
|
|
247
|
+
|
|
248
|
+
const refresh = useCallback(async () => {
|
|
249
|
+
if (!user) {
|
|
250
|
+
setStatus(null)
|
|
251
|
+
return
|
|
252
|
+
}
|
|
253
|
+
setLoading(true)
|
|
254
|
+
setError(null)
|
|
255
|
+
try {
|
|
256
|
+
const data = await auth.mfa.getStatus()
|
|
257
|
+
setStatus(data)
|
|
258
|
+
} catch (e) {
|
|
259
|
+
setError(e instanceof Error ? e : new Error(String(e)))
|
|
260
|
+
} finally {
|
|
261
|
+
setLoading(false)
|
|
262
|
+
}
|
|
263
|
+
}, [auth, user])
|
|
264
|
+
|
|
265
|
+
useEffect(() => {
|
|
266
|
+
refresh()
|
|
267
|
+
}, [refresh])
|
|
268
|
+
|
|
269
|
+
const verifyTotpEnrollment = useCallback(
|
|
270
|
+
async (code: string) => {
|
|
271
|
+
await auth.mfa.verifyTotpEnrollment(code)
|
|
272
|
+
await refresh()
|
|
273
|
+
},
|
|
274
|
+
[auth, refresh],
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
const disableTotp = useCallback(
|
|
278
|
+
async (currentPassword: string) => {
|
|
279
|
+
await auth.mfa.disableTotp(currentPassword)
|
|
280
|
+
await refresh()
|
|
281
|
+
},
|
|
282
|
+
[auth, refresh],
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
return {
|
|
286
|
+
status,
|
|
287
|
+
loading,
|
|
288
|
+
error,
|
|
289
|
+
refresh,
|
|
290
|
+
enrollTotp: () => auth.mfa.enrollTotp(),
|
|
291
|
+
verifyTotpEnrollment,
|
|
292
|
+
disableTotp,
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Reactive passkey list — list/delete/register, mutation sonrası
|
|
298
|
+
* otomatik refresh.
|
|
299
|
+
*/
|
|
300
|
+
export function usePasskeys(): {
|
|
301
|
+
passkeys: PasskeySummary[] | null
|
|
302
|
+
loading: boolean
|
|
303
|
+
error: Error | null
|
|
304
|
+
refresh: () => Promise<void>
|
|
305
|
+
register: (deviceName?: string) => Promise<void>
|
|
306
|
+
remove: (id: string) => Promise<void>
|
|
307
|
+
} {
|
|
308
|
+
const { auth, user } = useAuth()
|
|
309
|
+
const [passkeys, setPasskeys] = useState<PasskeySummary[] | null>(null)
|
|
310
|
+
const [loading, setLoading] = useState(false)
|
|
311
|
+
const [error, setError] = useState<Error | null>(null)
|
|
312
|
+
|
|
313
|
+
const refresh = useCallback(async () => {
|
|
314
|
+
if (!user) {
|
|
315
|
+
setPasskeys(null)
|
|
316
|
+
return
|
|
317
|
+
}
|
|
318
|
+
setLoading(true)
|
|
319
|
+
setError(null)
|
|
320
|
+
try {
|
|
321
|
+
const data = await auth.passkey.list()
|
|
322
|
+
setPasskeys(data)
|
|
323
|
+
} catch (e) {
|
|
324
|
+
setError(e instanceof Error ? e : new Error(String(e)))
|
|
325
|
+
} finally {
|
|
326
|
+
setLoading(false)
|
|
327
|
+
}
|
|
328
|
+
}, [auth, user])
|
|
329
|
+
|
|
330
|
+
useEffect(() => {
|
|
331
|
+
refresh()
|
|
332
|
+
}, [refresh])
|
|
333
|
+
|
|
334
|
+
const register = useCallback(
|
|
335
|
+
async (deviceName?: string) => {
|
|
336
|
+
await auth.passkey.register(deviceName)
|
|
337
|
+
await refresh()
|
|
338
|
+
},
|
|
339
|
+
[auth, refresh],
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
const remove = useCallback(
|
|
343
|
+
async (id: string) => {
|
|
344
|
+
await auth.passkey.delete(id)
|
|
345
|
+
await refresh()
|
|
346
|
+
},
|
|
347
|
+
[auth, refresh],
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
return { passkeys, loading, error, refresh, register, remove }
|
|
351
|
+
}
|
package/src/auth/types.ts
CHANGED
|
@@ -58,3 +58,69 @@ export class SentroyAuthError extends Error {
|
|
|
58
58
|
this.status = status
|
|
59
59
|
}
|
|
60
60
|
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Login response when MFA is enrolled — first step returns mfaRequired+
|
|
64
|
+
* mfaToken; second step `verifyMfa(mfaToken, code)` issues final tokens.
|
|
65
|
+
*/
|
|
66
|
+
export interface MfaChallengeResponse {
|
|
67
|
+
mfaRequired: true
|
|
68
|
+
mfaToken: string
|
|
69
|
+
factorType: "totp"
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Discriminated union — Login may resolve to tokens OR MFA challenge. */
|
|
73
|
+
export type LoginOutcome =
|
|
74
|
+
| { kind: "tokens"; data: LoginResponse }
|
|
75
|
+
| { kind: "mfa"; data: MfaChallengeResponse }
|
|
76
|
+
|
|
77
|
+
export interface SessionSummary {
|
|
78
|
+
id: string
|
|
79
|
+
refreshTokenPrefix: string
|
|
80
|
+
userAgent: string | null
|
|
81
|
+
ip: string | null
|
|
82
|
+
expiresAt: string
|
|
83
|
+
createdAt: string
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface ActivityEntry {
|
|
87
|
+
id: string
|
|
88
|
+
action: string
|
|
89
|
+
ipAddress: string | null
|
|
90
|
+
createdAt: string
|
|
91
|
+
details: Record<string, unknown> | null
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface MfaStatus {
|
|
95
|
+
enrolled: boolean
|
|
96
|
+
factorType?: "totp"
|
|
97
|
+
verifiedAt?: string | null
|
|
98
|
+
recoveryCodesRemaining?: number
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface MfaEnrollResponse {
|
|
102
|
+
secret: string
|
|
103
|
+
otpauthUri: string
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface MfaVerifyEnrollmentResponse {
|
|
107
|
+
enrolled: true
|
|
108
|
+
recoveryCodes: string[]
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export interface PasskeySummary {
|
|
112
|
+
id: string
|
|
113
|
+
credentialIdPrefix: string
|
|
114
|
+
deviceName: string | null
|
|
115
|
+
transports: string[]
|
|
116
|
+
lastUsedAt: string | null
|
|
117
|
+
createdAt: string
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export type SocialProvider =
|
|
121
|
+
| "google"
|
|
122
|
+
| "github"
|
|
123
|
+
| "facebook"
|
|
124
|
+
| "microsoft"
|
|
125
|
+
| "twitter"
|
|
126
|
+
| "apple"
|