@lalternative/auth 0.13.3 → 0.15.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 +115 -0
- package/dist/client.d.ts +1 -1
- package/dist/index.d.ts +21 -4
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +96 -5
- package/dist/server.js +364 -3
- package/dist/server.js.map +1 -1
- package/dist/{sso-profile-HO-u17jd.d.ts → sso-profile-aNyHaZHJ.d.ts} +1 -1
- package/dist/{types-3OHf736K.d.ts → types-COX3VaBw.d.ts} +39 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -186,3 +186,118 @@ if (isInvitationFailure(outcome)) return <InvitationNotice reason={outcome} />
|
|
|
186
186
|
|
|
187
187
|
`endpoint` is any backend that redeems a token, so an app already claiming
|
|
188
188
|
against its own API keeps doing so; `extra` adds fields to the request body.
|
|
189
|
+
|
|
190
|
+
## Customer passwords at the identity provider (0.14.0, completed in 0.15.0)
|
|
191
|
+
|
|
192
|
+
From **0.14.0** — and working end to end from **0.15.0**, which relays the
|
|
193
|
+
password to the provider instead of dropping it — an app can move its customers' passwords to the suite's
|
|
194
|
+
identity provider while keeping its own login screen, its own domain and its
|
|
195
|
+
own session. Nothing is enabled by a version bump alone: passwords stay local
|
|
196
|
+
until `kratosPasswords` is passed. An app upgrading to 0.14.x never changes
|
|
197
|
+
behaviour by accident.
|
|
198
|
+
|
|
199
|
+
```ts
|
|
200
|
+
createPlatformAuth({
|
|
201
|
+
// …
|
|
202
|
+
kratosPasswords: {
|
|
203
|
+
publicUrl: process.env.URBANGATE_PUBLIC_URL!,
|
|
204
|
+
issuer: process.env.URBANGATE_ISSUER_URL!,
|
|
205
|
+
clientId: process.env.URBANGATE_PROVISIONER_CLIENT_ID!,
|
|
206
|
+
clientSecret: process.env.URBANGATE_PROVISIONER_CLIENT_SECRET!,
|
|
207
|
+
role: "spore:user",
|
|
208
|
+
product: "spore",
|
|
209
|
+
onProvisioningDeferred: ({ userId, email }) => queueIdentityRepair(userId, email),
|
|
210
|
+
},
|
|
211
|
+
})
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The login form, its copy and its routes do not change, and nobody is
|
|
215
|
+
redirected: the password is posted to this app as before and checked against
|
|
216
|
+
Kratos instead of a local hash.
|
|
217
|
+
|
|
218
|
+
### Each app keeps its own accounts
|
|
219
|
+
|
|
220
|
+
The same person signing up on two products gets two local users and two
|
|
221
|
+
passwords, which may use two different addresses. They are never told the
|
|
222
|
+
products know each other. What they share — when the address is the same — is
|
|
223
|
+
one identity at the provider, which is what an app key is issued against.
|
|
224
|
+
|
|
225
|
+
An app therefore **must not deactivate or delete the identity** when it
|
|
226
|
+
deletes a local account: it drops its own role and its local row. Deactivating
|
|
227
|
+
the identity would sign the person out of every other product of the suite.
|
|
228
|
+
|
|
229
|
+
The address is what joins the two, and nothing else does. Someone who signs up
|
|
230
|
+
on spore with one address and on lalter with another gets **two identities**,
|
|
231
|
+
and the provider has no way to know they are the same person. Their app keys
|
|
232
|
+
are then split across those identities: `/keys` shows each set on its own, and
|
|
233
|
+
a key minted under one cannot name the other's product. That follows from each
|
|
234
|
+
app keeping its own accounts, and is not a defect to route around — but an
|
|
235
|
+
integrator who used two addresses will meet it, and the answer is to sign up
|
|
236
|
+
with the same address on both products.
|
|
237
|
+
|
|
238
|
+
### Refusals a form must tell apart
|
|
239
|
+
|
|
240
|
+
`res.error.message` carries the reason, so the existing error banner renders
|
|
241
|
+
it with no change. A page that routes rather than renders uses the predicates:
|
|
242
|
+
|
|
243
|
+
| Predicate | Meaning |
|
|
244
|
+
|---|---|
|
|
245
|
+
| `needsPasswordRecovery` | The identity has no password yet (an account predating the move). Send to recovery — it is **not** a wrong password. |
|
|
246
|
+
| `isIdentityProviderUnavailable` | The provider is unreachable. The password was never refused; do not suggest changing it. |
|
|
247
|
+
| `needsSecondFactor` | Kratos requires a second factor. |
|
|
248
|
+
| `isAccountDisabled` | The identity is deactivated. |
|
|
249
|
+
|
|
250
|
+
Verification fails closed: only an explicit refusal by Kratos reads as a wrong
|
|
251
|
+
password, and an outage answers 503 so nobody rotates a password that was
|
|
252
|
+
right.
|
|
253
|
+
|
|
254
|
+
### The password reaches the provider, or the write fails
|
|
255
|
+
|
|
256
|
+
Kratos holds the password, so every write must reach it: sign-up, the OTP
|
|
257
|
+
reset and a password change all relay what the form collected before anything
|
|
258
|
+
is stored locally. Kratos hashes it with the hasher its own configuration
|
|
259
|
+
declares, so an app cannot hand over one it hashed itself.
|
|
260
|
+
|
|
261
|
+
If the provider is unreachable the write fails with 503 and nothing changes,
|
|
262
|
+
rather than storing a placeholder against a password Kratos never received —
|
|
263
|
+
that account could never be opened again, and nothing would say why.
|
|
264
|
+
|
|
265
|
+
This is the one place the package refuses rather than degrades. Reading
|
|
266
|
+
(signing in) fails closed too, but a sign-up that cannot reach the provider
|
|
267
|
+
still creates the local account: the person is registered, `identityId` stays
|
|
268
|
+
null, and `onProvisioningDeferred` hands the repair to the app. Writing a
|
|
269
|
+
password has no such fallback, because a password stored nowhere is not a
|
|
270
|
+
state a repair can fix.
|
|
271
|
+
|
|
272
|
+
### Why the sentinel hash
|
|
273
|
+
|
|
274
|
+
Better Auth's `/sign-in/email` reads the credential row and refuses **before**
|
|
275
|
+
reaching the verifier when it carries no hash, and its verifier is handed only
|
|
276
|
+
`{hash, password}` — never the address. So the package writes
|
|
277
|
+
`KRATOS_SENTINEL_HASH` in place of a hash and carries the address to the
|
|
278
|
+
verifier from the route hook.
|
|
279
|
+
|
|
280
|
+
The sentinel is a constant, not a hash: argon2/bcrypt/scrypt verification of
|
|
281
|
+
it fails on its format, so a build that ever bypassed the custom verifier
|
|
282
|
+
refuses everyone rather than admitting anyone.
|
|
283
|
+
|
|
284
|
+
This is deliberate and it is a workaround. When Better Auth exposes a seam for
|
|
285
|
+
an external credential provider, the replacement is to handle `/sign-in/email`
|
|
286
|
+
before the native route runs, and the sentinel disappears. That was not taken
|
|
287
|
+
now because it means re-implementing session creation, which is where a
|
|
288
|
+
mistake becomes an authentication hole.
|
|
289
|
+
|
|
290
|
+
### Provisioning is not atomic
|
|
291
|
+
|
|
292
|
+
`user.create.after` runs after the insert commits, so a sign-up cannot be
|
|
293
|
+
atomic with the identity it needs. A provider that is down leaves `identityId`
|
|
294
|
+
null and the person registered all the same — a customer is never refused
|
|
295
|
+
registration because the provider is unavailable. `onProvisioningDeferred`
|
|
296
|
+
receives those sign-ups so the app can queue the repair, which re-sends
|
|
297
|
+
through `provisionIdentity`. The endpoint is idempotent on the address, so a
|
|
298
|
+
repair for someone who already got an identity returns that same one.
|
|
299
|
+
|
|
300
|
+
### Rollout
|
|
301
|
+
|
|
302
|
+
Per app, smallest customer base first — never all at once. An app that
|
|
303
|
+
switches and breaks locks its customers out.
|
package/dist/client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createAuthClient } from 'better-auth/react';
|
|
2
|
-
import { f as AuthClientSurface, l as MagicLinkClientSurface, S as SsoClientSurface, d as AdminClientSurface, T as TwoFactorClientSurface, P as PlatformAuthClientConfig } from './types-
|
|
2
|
+
import { f as AuthClientSurface, l as MagicLinkClientSurface, S as SsoClientSurface, d as AdminClientSurface, T as TwoFactorClientSurface, P as PlatformAuthClientConfig } from './types-COX3VaBw.js';
|
|
3
3
|
import 'better-auth';
|
|
4
4
|
|
|
5
5
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { L as LoginFormProps, R as RegisterFormProps, V as VerifyEmailFormProps, F as ForgotPasswordFormProps, M as MagicLinkFormProps, a as ResetPasswordFormProps, A as AuthLayoutProps, I as InvitationNoticeProps, b as AuthClientResult, c as LinkComponent } from './types-
|
|
2
|
-
export { d as AdminClientSurface, e as AuthClientDataResult, f as AuthClientSurface, g as AuthInviteProps, h as AuthNavProps, i as AuthThemeProps, j as InvitationFailure, k as LoginFormLabels, l as MagicLinkClientSurface, m as MagicLinkConfig, n as MagicLinkFormLabels, P as PlatformAuthClientConfig, o as PlatformAuthConfig, p as PlatformAuthMailer, q as PlatformAuthMailerArgs, r as PlatformAuthMailerType, s as
|
|
1
|
+
import { L as LoginFormProps, R as RegisterFormProps, V as VerifyEmailFormProps, F as ForgotPasswordFormProps, M as MagicLinkFormProps, a as ResetPasswordFormProps, A as AuthLayoutProps, I as InvitationNoticeProps, b as AuthClientResult, c as LinkComponent } from './types-COX3VaBw.js';
|
|
2
|
+
export { d as AdminClientSurface, e as AuthClientDataResult, f as AuthClientSurface, g as AuthInviteProps, h as AuthNavProps, i as AuthThemeProps, j as InvitationFailure, k as LoginFormLabels, l as MagicLinkClientSurface, m as MagicLinkConfig, n as MagicLinkFormLabels, P as PlatformAuthClientConfig, o as PlatformAuthConfig, p as PlatformAuthMailer, q as PlatformAuthMailerArgs, r as PlatformAuthMailerType, s as PlatformKratosPasswordConfig, t as PlatformRateLimitConfig, u as PlatformRateLimitRule, v as PlatformSession, w as PlatformSessionData, x as PlatformSsoConfig, y as PlatformTwoFactorConfig, z as PlatformUser, B as RegisterFormLabels, S as SsoClientSurface, T as TwoFactorClientSurface } from './types-COX3VaBw.js';
|
|
3
3
|
import * as better_auth_react from 'better-auth/react';
|
|
4
4
|
import * as better_auth from 'better-auth';
|
|
5
5
|
import { PlatformAuthClient } from './client.js';
|
|
6
6
|
export { startSso } from './client.js';
|
|
7
7
|
import * as react from 'react';
|
|
8
8
|
import { InputHTMLAttributes, ReactNode } from 'react';
|
|
9
|
-
export { C as ClaimOutcome, S as SsoMappedUser, a as SsoProfile, i as isInvitationFailure, m as mapSsoProfile } from './sso-profile-
|
|
9
|
+
export { C as ClaimOutcome, S as SsoMappedUser, a as SsoProfile, i as isInvitationFailure, m as mapSsoProfile } from './sso-profile-aNyHaZHJ.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* Returns a useSession hook bound to the given auth client.
|
|
@@ -221,6 +221,23 @@ declare function withInviteToken(href: string, token?: string): string;
|
|
|
221
221
|
*/
|
|
222
222
|
declare function isEmailNotVerified(error: AuthClientResult["error"]): boolean;
|
|
223
223
|
|
|
224
|
+
/**
|
|
225
|
+
* Whether the sign-in was refused because the person's identity carries no
|
|
226
|
+
* password yet at the provider — an account that predates the move, whose
|
|
227
|
+
* owner sets a password once through the recovery flow.
|
|
228
|
+
*
|
|
229
|
+
* It is not a wrong password, and rendering it as one sends the person
|
|
230
|
+
* retrying an old password that will never work again.
|
|
231
|
+
*/
|
|
232
|
+
declare function needsPasswordRecovery(error: AuthClientResult["error"]): boolean;
|
|
233
|
+
/**
|
|
234
|
+
* Whether the identity provider could not be reached. The password was never
|
|
235
|
+
* refused: the person retries, and must not be told to change it.
|
|
236
|
+
*/
|
|
237
|
+
declare function isIdentityProviderUnavailable(error: AuthClientResult["error"]): boolean;
|
|
238
|
+
declare function needsSecondFactor(error: AuthClientResult["error"]): boolean;
|
|
239
|
+
declare function isAccountDisabled(error: AuthClientResult["error"]): boolean;
|
|
240
|
+
|
|
224
241
|
type OAuthErrorLabels = {
|
|
225
242
|
accountNotLinked: string;
|
|
226
243
|
socialCancelled: string;
|
|
@@ -326,4 +343,4 @@ interface AuthLinkProps {
|
|
|
326
343
|
*/
|
|
327
344
|
declare function AuthLink({ to, as: Link, className, children }: AuthLinkProps): react.JSX.Element;
|
|
328
345
|
|
|
329
|
-
export { AuthClientResult, AuthField, type AuthFieldProps, AuthLayout, AuthLayoutProps, AuthLink, AuthSubmit, ForgotPasswordForm, ForgotPasswordFormProps, InvitationNotice, InvitationNoticeProps, LinkComponent, LoginForm, LoginFormProps, type MagicLinkErrorLabels, MagicLinkForm, MagicLinkFormProps, type OAuthErrorLabels, RegisterForm, RegisterFormProps, ResetPasswordForm, ResetPasswordFormProps, SocialButtons, VerifyEmailForm, VerifyEmailFormProps, clearOAuthError, initialMagicLinkError, initialOAuthError, isEmailNotVerified, isMagicLinkError, magicLinkErrorCallback, magicLinkErrorMessage, normalizeInviteToken, oauthErrorCallback, oauthErrorMessage, useLogout, useSession, withInviteToken };
|
|
346
|
+
export { AuthClientResult, AuthField, type AuthFieldProps, AuthLayout, AuthLayoutProps, AuthLink, AuthSubmit, ForgotPasswordForm, ForgotPasswordFormProps, InvitationNotice, InvitationNoticeProps, LinkComponent, LoginForm, LoginFormProps, type MagicLinkErrorLabels, MagicLinkForm, MagicLinkFormProps, type OAuthErrorLabels, RegisterForm, RegisterFormProps, ResetPasswordForm, ResetPasswordFormProps, SocialButtons, VerifyEmailForm, VerifyEmailFormProps, clearOAuthError, initialMagicLinkError, initialOAuthError, isAccountDisabled, isEmailNotVerified, isIdentityProviderUnavailable, isMagicLinkError, magicLinkErrorCallback, magicLinkErrorMessage, needsPasswordRecovery, needsSecondFactor, normalizeInviteToken, oauthErrorCallback, oauthErrorMessage, useLogout, useSession, withInviteToken };
|
package/dist/index.js
CHANGED
|
@@ -1514,6 +1514,20 @@ function InvitationNotice({
|
|
|
1514
1514
|
action
|
|
1515
1515
|
] });
|
|
1516
1516
|
}
|
|
1517
|
+
|
|
1518
|
+
// src/kratos-sign-in-error.ts
|
|
1519
|
+
function needsPasswordRecovery(error) {
|
|
1520
|
+
return error?.code === "IDENTITY_HAS_NO_PASSWORD";
|
|
1521
|
+
}
|
|
1522
|
+
function isIdentityProviderUnavailable(error) {
|
|
1523
|
+
return error?.code === "IDENTITY_PROVIDER_UNAVAILABLE" || error?.status === 503;
|
|
1524
|
+
}
|
|
1525
|
+
function needsSecondFactor(error) {
|
|
1526
|
+
return error?.code === "SECOND_FACTOR_REQUIRED";
|
|
1527
|
+
}
|
|
1528
|
+
function isAccountDisabled(error) {
|
|
1529
|
+
return error?.code === "ACCOUNT_DISABLED";
|
|
1530
|
+
}
|
|
1517
1531
|
export {
|
|
1518
1532
|
AuthField,
|
|
1519
1533
|
AuthLayout,
|
|
@@ -1530,12 +1544,16 @@ export {
|
|
|
1530
1544
|
clearOAuthError,
|
|
1531
1545
|
initialMagicLinkError,
|
|
1532
1546
|
initialOAuthError,
|
|
1547
|
+
isAccountDisabled,
|
|
1533
1548
|
isEmailNotVerified,
|
|
1549
|
+
isIdentityProviderUnavailable,
|
|
1534
1550
|
isInvitationFailure,
|
|
1535
1551
|
isMagicLinkError,
|
|
1536
1552
|
magicLinkErrorCallback,
|
|
1537
1553
|
magicLinkErrorMessage,
|
|
1538
1554
|
mapSsoProfile,
|
|
1555
|
+
needsPasswordRecovery,
|
|
1556
|
+
needsSecondFactor,
|
|
1539
1557
|
normalizeInviteToken,
|
|
1540
1558
|
oauthErrorCallback,
|
|
1541
1559
|
oauthErrorMessage,
|