@aurabase/auth 0.4.0 → 0.5.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/README.md +96 -10
- package/dist/index.cjs +391 -211
- package/dist/index.d.cts +160 -88
- package/dist/index.d.ts +160 -88
- package/dist/index.js +392 -212
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ Dans l'usage courant, `AuthService` n'est jamais instancié directement : il est
|
|
|
23
23
|
```ts
|
|
24
24
|
import { createClient } from '@aurabase/aurabase-js'
|
|
25
25
|
|
|
26
|
-
const aura = createClient('
|
|
26
|
+
const aura = createClient('https://mon-projet.aurabase.cloud', 'aura_anon_xxx')
|
|
27
27
|
```
|
|
28
28
|
|
|
29
29
|
### Mot de passe
|
|
@@ -48,7 +48,10 @@ const refreshed = await aura.auth.refreshSession()
|
|
|
48
48
|
await aura.auth.setSession('stored-access-token', 'stored-refresh-token')
|
|
49
49
|
|
|
50
50
|
const sessions = await aura.auth.listSessions()
|
|
51
|
-
|
|
51
|
+
// `data` peut être `null` même en succès (204 / corps vide) : on le teste plutôt
|
|
52
|
+
// que `error`, ce qui évite l'assertion `!` et fait narrower TypeScript.
|
|
53
|
+
const first = sessions.data?.[0]
|
|
54
|
+
if (first) await aura.auth.revokeSession(first.id)
|
|
52
55
|
await aura.auth.revokeAllSessions()
|
|
53
56
|
```
|
|
54
57
|
|
|
@@ -64,20 +67,103 @@ const { data, error } = await aura.auth.signInWithOAuth('github', {
|
|
|
64
67
|
})
|
|
65
68
|
|
|
66
69
|
if (error) throw error
|
|
70
|
+
if (!data) throw new Error("aucune URL de redirection renvoyée")
|
|
67
71
|
|
|
68
|
-
// Côté serveur / SSR : pas de redirection automatique, il faut rediriger manuellement vers data
|
|
69
|
-
console.log(data
|
|
72
|
+
// Côté serveur / SSR : pas de redirection automatique, il faut rediriger manuellement vers data.url
|
|
73
|
+
console.log(data.url)
|
|
70
74
|
```
|
|
71
75
|
|
|
76
|
+
#### `queryParams` — ce qui atteint vraiment le provider
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
await aura.auth.signInWithOAuth('google', {
|
|
80
|
+
queryParams: { prompt: 'consent', access_type: 'offline', login_hint: 'user@example.com' },
|
|
81
|
+
})
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Ces clés-là — et elles seules — sont recopiées dans l'URL d'autorisation du provider :
|
|
85
|
+
|
|
86
|
+
| Clé | Origine |
|
|
87
|
+
| --- | --- |
|
|
88
|
+
| `prompt`, `login_hint`, `display`, `ui_locales`, `max_age`, `acr_values`, `claims_locales` | OpenID Connect Core §3.1.2.1 |
|
|
89
|
+
| `access_type`, `include_granted_scopes`, `hd` | Google OAuth 2.0 |
|
|
90
|
+
|
|
91
|
+
**Toute autre clé est refusée**, jamais transmise et jamais ignorée en silence : la méthode
|
|
92
|
+
renvoie `error.code === 'invalid_oauth_query_param'` sans déclencher de redirection.
|
|
93
|
+
|
|
94
|
+
> `hd` est un **indice** d'affichage Google, pas un contrôle : il n'empêche pas de se
|
|
95
|
+
> connecter avec un autre domaine. Pour restreindre pour de bon, vérifiez le domaine de
|
|
96
|
+
> l'utilisateur au retour du flux.
|
|
97
|
+
|
|
98
|
+
- Les paramètres du protocole (`state`, `nonce`, `client_id`, `response_type`, `scope`,
|
|
99
|
+
`code_challenge`, `code_challenge_method`, `response_mode`…) sont **calculés par le
|
|
100
|
+
serveur** : les laisser fournir par un client permettrait de détourner le flux.
|
|
101
|
+
- Les paramètres de la requête (`redirect_uri`, `scopes`) ont leur option dédiée
|
|
102
|
+
(`redirectTo`, `scopes`). `project_id` n'est pas fournissable non plus : le projet est
|
|
103
|
+
celui de l'URL passée à `createClient()`.
|
|
104
|
+
- Les variantes de casse (`Prompt`, `State`) et les clés inconnues sont refusées aussi.
|
|
105
|
+
|
|
106
|
+
`redirectTo` part en `redirect_uri` (le backend accepte aussi l'alias `redirect_to`, nom
|
|
107
|
+
employé par les SDK Dart/Python/Rust) : il est validé contre les URLs autorisées du projet
|
|
108
|
+
et ne remplace jamais le `redirect_uri` envoyé au provider, qui reste le callback Aurabase.
|
|
109
|
+
|
|
110
|
+
#### Où atterrit l'utilisateur au retour
|
|
111
|
+
|
|
112
|
+
| Cas | URL servie |
|
|
113
|
+
| --- | --- |
|
|
114
|
+
| `redirectTo` fournie, connexion aboutie | `{redirectTo}#access_token=…&refresh_token=…&expires_in=…&token_type=bearer&user=…` — **l'URL exacte**, aucun segment ajouté |
|
|
115
|
+
| `redirectTo` omise, connexion aboutie | `{site_url du projet}/oauth/callback#access_token=…` (`site_url` est une base, pas une page) |
|
|
116
|
+
| **Utilisateur avec MFA actif** | `{redirectTo}/oauth/mfa#mfa_token=…` — ou `{site_url}/oauth/mfa#mfa_token=…` si `redirectTo` est omise |
|
|
117
|
+
|
|
118
|
+
L'utilisateur MFA n'a pas fini de se connecter : il reste un défi à relever, et `#mfa_token`
|
|
119
|
+
n'est pas une session. Le backend l'envoie donc sur un **sous-chemin dédié**, `…/oauth/mfa`,
|
|
120
|
+
plutôt que sur votre page d'atterrissage où le jeton ne serait attendu par personne. Ce
|
|
121
|
+
sous-chemin reste sous l'origine et le préfixe de chemin validés : il n'échappe pas à
|
|
122
|
+
l'allowlist.
|
|
123
|
+
|
|
124
|
+
Votre application doit donc servir une page sur `{redirectTo}/oauth/mfa`. Le SDK y fait le
|
|
125
|
+
reste : `detectSessionInUrl` reconnaît `#mfa_token`, émet `MFA_CHALLENGE` avec `mfaToken`
|
|
126
|
+
(**aucune session n'est posée**) et nettoie l'URL.
|
|
127
|
+
|
|
128
|
+
```ts
|
|
129
|
+
aura.auth.onAuthStateChange(async ({ event, mfaToken }) => {
|
|
130
|
+
if (event !== 'MFA_CHALLENGE' || !mfaToken) return
|
|
131
|
+
const code = await demanderLeCodeTotp() // votre UI
|
|
132
|
+
await aura.auth.mfaVerify({ mfa_token: mfaToken, code }) // → SIGNED_IN
|
|
133
|
+
})
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
> **Divergence Supabase assumée.** Une `redirectTo` **hors allowlist** fait échouer le
|
|
137
|
+
> démarrage (`400`). Supabase, lui, l'ignore en silence et retombe sur le Site URL. Aurabase
|
|
138
|
+
> préfère l'erreur explicite : une redirection silencieuse vers une autre destination que
|
|
139
|
+
> celle demandée est indétectable côté application.
|
|
140
|
+
|
|
141
|
+
La décision appartient au **backend** (`GET /v1/auth/oauth/{provider}/start`, qui répond
|
|
142
|
+
`400` sur les mêmes règles, plus le refus des doublons, des valeurs vides, démesurées ou
|
|
143
|
+
porteuses d'un caractère de contrôle) : cet endpoint est public, une allowlist purement
|
|
144
|
+
cliente ne protégerait rien. Le filtre du SDK n'en est que le miroir — un test de parité lit
|
|
145
|
+
la source Rust pour interdire toute divergence — et sert à échouer avant la redirection.
|
|
146
|
+
|
|
72
147
|
Retour du provider (flux PKCE) :
|
|
73
148
|
|
|
74
149
|
```ts
|
|
75
|
-
|
|
150
|
+
import { isMfaChallenge } from '@aurabase/auth'
|
|
151
|
+
|
|
152
|
+
const { data, error } = await aura.auth.exchangeCodeForSession('auth-code-from-callback')
|
|
76
153
|
|
|
77
154
|
// Mobile natif : échange d'un ID token provider (Google/Apple) contre une session Aurabase
|
|
78
155
|
await aura.auth.signInWithIdToken('google', 'id-token-from-provider')
|
|
79
156
|
```
|
|
80
157
|
|
|
158
|
+
Ces deux méthodes renvoient un `LoginResponse` (`TokenPair | MfaChallenge`), pas un `TokenPair` :
|
|
159
|
+
sur un utilisateur avec MFA actif, le backend répond `{ mfa_required: true, mfa_token }` et
|
|
160
|
+
**aucune session n'est enregistrée** — `MFA_CHALLENGE` est émis, à relever par `mfaVerify`.
|
|
161
|
+
Discriminez avant de lire les jetons :
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
if (data && !isMfaChallenge(data)) console.log(data.access_token)
|
|
165
|
+
```
|
|
166
|
+
|
|
81
167
|
### Magic link / OTP
|
|
82
168
|
|
|
83
169
|
```ts
|
|
@@ -85,10 +171,10 @@ await aura.auth.signInWithMagicLink({ email: 'user@example.com', redirect_url: '
|
|
|
85
171
|
await aura.auth.verifyMagicLink('token-from-link')
|
|
86
172
|
|
|
87
173
|
await aura.auth.signInWithEmailOtp('user@example.com')
|
|
88
|
-
await aura.auth.
|
|
174
|
+
await aura.auth.verifyOtp({ type: 'email', email: 'user@example.com', token: '123456' })
|
|
89
175
|
|
|
90
176
|
await aura.auth.signInWithSmsOtp('+15551234567')
|
|
91
|
-
await aura.auth.
|
|
177
|
+
await aura.auth.verifyOtp({ type: 'sms', phone: '+15551234567', token: '123456' })
|
|
92
178
|
```
|
|
93
179
|
|
|
94
180
|
### Anonyme
|
|
@@ -145,9 +231,9 @@ via `history.replaceState`).
|
|
|
145
231
|
### Vérification d'email
|
|
146
232
|
|
|
147
233
|
```ts
|
|
148
|
-
await aura.auth.
|
|
234
|
+
await aura.auth.verifyOtp({ type: 'signup', token: 'token-from-email' })
|
|
149
235
|
await aura.auth.resendVerificationEmail() // reprend l'email de la session courante si omis
|
|
150
|
-
await aura.auth.
|
|
236
|
+
await aura.auth.verifyOtp({ type: 'email_change', token: 'token-from-email' })
|
|
151
237
|
```
|
|
152
238
|
|
|
153
239
|
### MFA (TOTP)
|
|
@@ -192,7 +278,7 @@ Réservé au serveur (clé `service_role`). Deux façons d'y accéder :
|
|
|
192
278
|
// Via le client admin (recommandé)
|
|
193
279
|
import { createAdminClient } from '@aurabase/aurabase-js'
|
|
194
280
|
|
|
195
|
-
const admin = createAdminClient('
|
|
281
|
+
const admin = createAdminClient('https://mon-projet.aurabase.cloud', process.env.SERVICE_ROLE_KEY!)
|
|
196
282
|
await admin.authAdmin.listUsers({ limit: 50 })
|
|
197
283
|
```
|
|
198
284
|
|