@dloizides/auth-web 1.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.
- package/CHANGELOG.md +120 -0
- package/LICENSE +21 -0
- package/README.md +229 -0
- package/dist/index.d.mts +806 -0
- package/dist/index.d.ts +806 -0
- package/dist/index.js +1413 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1373 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +110 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 1.2.0 (2026-05-22)
|
|
4
|
+
|
|
5
|
+
Additive release for Phase 3d of the unified-auth plan — the event-scoped PIN
|
|
6
|
+
UI. Adds the native, branded event-PIN login surface so an app (Kefi) can offer
|
|
7
|
+
"sign in with your event PIN" for operational staff (door / DJ / media) without
|
|
8
|
+
the user ever bouncing to Keycloak's hosted UI. No breaking changes; every
|
|
9
|
+
1.1.0 export is unchanged.
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **`<PinForm>`** — the ready-made, themeable single-step event-PIN login form,
|
|
14
|
+
built on `usePinLogin`. A PIN field + a "sign in" button; the
|
|
15
|
+
`eventExternalId` is a prop (the event context comes from the route/page, it
|
|
16
|
+
is never typed by the user). Styled by the same `AuthTheme` token bag as the
|
|
17
|
+
other forms; copy is supplied through the i18n-agnostic `PinFormLabels` bag
|
|
18
|
+
(English `DEFAULT_PIN_LABELS` fallback). `onSuccess` hands back the signed-in
|
|
19
|
+
`BffUser`.
|
|
20
|
+
- **`usePinLogin`** — the headless PIN hook (the custom-layout escape hatch). A
|
|
21
|
+
single-step flow: `submit(pin)` → `POST /bff/pin/login` → `BffUser`, with
|
|
22
|
+
`reset()`. Mirrors `useOtpLogin` (mounted-guard, memoised result,
|
|
23
|
+
latest-`eventExternalId` ref).
|
|
24
|
+
- **`PinFormLabels`** + **`DEFAULT_PIN_LABELS`** — the PIN label bag.
|
|
25
|
+
- New `AuthTestIds` entries for every PIN interactive element.
|
|
26
|
+
- Re-exports `BffPinLoginRequest` from `@dloizides/auth-client`.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- Peer dependency `@dloizides/auth-client` raised to `>=3.2.0` — the version
|
|
31
|
+
that adds `BffAuthClient.pinLogin`.
|
|
32
|
+
|
|
33
|
+
## 1.1.0 (2026-05-22)
|
|
34
|
+
|
|
35
|
+
Additive release for Phase 2d of the unified-auth plan — the email-OTP UI. Adds
|
|
36
|
+
the native, branded email-OTP login surface so an app can offer "sign in with a
|
|
37
|
+
code" without the user ever bouncing to Keycloak's hosted UI. No breaking
|
|
38
|
+
changes; every 1.0.0 export is unchanged.
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- **`<OtpForm>`** — the ready-made, themeable two-step email-OTP login form,
|
|
43
|
+
built on `useOtpLogin`. Step 1 collects the email and sends a code; step 2
|
|
44
|
+
collects the code, verifies it, and offers "resend code" and "use a different
|
|
45
|
+
email". Styled by the same `AuthTheme` token bag as the other forms; copy is
|
|
46
|
+
supplied through the i18n-agnostic `OtpFormLabels` bag (English
|
|
47
|
+
`DEFAULT_OTP_LABELS` fallback). `onSuccess` hands back the signed-in `BffUser`.
|
|
48
|
+
- **`useOtpLogin`** — the headless OTP hook (the custom-layout escape hatch). A
|
|
49
|
+
small forward state machine over `OtpLoginStep`: `requestCode(identifier)` →
|
|
50
|
+
`EnterCode`, then `verifyCode(otp)` → `BffUser`, with `resend()` and
|
|
51
|
+
`reset()`. Mirrors `useBffAuth` (mounted-guard, memoised result).
|
|
52
|
+
- **`OtpLoginStep`** — the `RequestCode` / `EnterCode` step enum.
|
|
53
|
+
- **`OtpFormLabels`** + **`DEFAULT_OTP_LABELS`** — the OTP label bag.
|
|
54
|
+
- New `AuthTestIds` entries for every OTP interactive element.
|
|
55
|
+
- Re-exports `BffOtpRequestRequest`, `BffOtpVerifyRequest` and
|
|
56
|
+
`BffOtpRequestResult` from `@dloizides/auth-client`.
|
|
57
|
+
|
|
58
|
+
### Changed
|
|
59
|
+
|
|
60
|
+
- Peer dependency `@dloizides/auth-client` raised to `>=3.1.0` — the version
|
|
61
|
+
that ships `BffAuthClient.requestOtp` / `.verifyOtp` (`POST /bff/otp/request`
|
|
62
|
+
and `POST /bff/otp/verify`).
|
|
63
|
+
|
|
64
|
+
### Notes
|
|
65
|
+
|
|
66
|
+
- Still strictly thin: UI + the same-origin `/bff/*` client. The OTP client
|
|
67
|
+
methods carry the `X-BFF-Csrf` header like every other state-changing call;
|
|
68
|
+
no token handling, no secrets in the browser.
|
|
69
|
+
- This release adds the OTP surface to the package only. Wiring an app onto it
|
|
70
|
+
(the Kefi pilot) is Phase 3.
|
|
71
|
+
|
|
72
|
+
## 1.0.0 (2026-05-22)
|
|
73
|
+
|
|
74
|
+
Initial release — the frontend half of the unified-auth plan (Phase 1b).
|
|
75
|
+
|
|
76
|
+
`@dloizides/auth-web` is the shared, themeable, branded auth UI for the
|
|
77
|
+
dloizides.com portfolio. It is extracted and generalised from the per-app auth
|
|
78
|
+
code that lived inside `apps/katalogos-web` so katalogos, erevna and kefi can
|
|
79
|
+
share one implementation instead of copy-pasting auth adapters.
|
|
80
|
+
|
|
81
|
+
### Added
|
|
82
|
+
|
|
83
|
+
- **Themeable components**
|
|
84
|
+
- `<LoginForm>` — password login, built on `useBffAuth`.
|
|
85
|
+
- `<ForgotPasswordForm>` — request-a-reset-link, built on
|
|
86
|
+
`useBffForgotPassword`; no-enumeration confirmation state.
|
|
87
|
+
- `<ResetPasswordForm>` — choose-a-new-password, built on
|
|
88
|
+
`useResetPasswordForm`.
|
|
89
|
+
- All three are styled by an `AuthTheme` design-token bag so visually
|
|
90
|
+
distinct apps share one implementation.
|
|
91
|
+
- **Theming** — `AuthThemeProvider`, `useAuthTheme`, `defaultAuthTheme`, and
|
|
92
|
+
the `AuthTheme` token contract (colors / radii / spacing / typography).
|
|
93
|
+
Precedence: explicit `theme` prop → `<AuthThemeProvider>` context → default.
|
|
94
|
+
- **i18n-agnostic label bags** — `LoginFormLabels`, `ForgotPasswordFormLabels`,
|
|
95
|
+
`ResetPasswordFormLabels` plus English `DEFAULT_*` constants. The package
|
|
96
|
+
ships no `t()` / `FM()`; consuming apps pass already-localised strings.
|
|
97
|
+
- **Headless hooks** (the custom-layout escape hatch)
|
|
98
|
+
- `useBffAuth` — login / logout / `/bff/me` session, lifecycle status.
|
|
99
|
+
- `useBffForgotPassword`, `useBffResetPassword` — React Query mutations.
|
|
100
|
+
- `useResetPasswordForm` — reset-password form-state + validation logic.
|
|
101
|
+
- **`createBffAuthClient`** — one-line wiring of a same-origin `BffAuthClient`
|
|
102
|
+
with a lazily-resolved `fetch` (load-time side-effect-free).
|
|
103
|
+
- **Role-based post-login router** — `resolvePostLoginRoute(user, table)` and
|
|
104
|
+
`collectUserRoles`. The consuming app supplies the role → route table.
|
|
105
|
+
- **Password policy** — `validatePasswordPolicy`, `isPasswordValid`,
|
|
106
|
+
`PasswordPolicyError`; mirrors the TenantService backend rules.
|
|
107
|
+
- **Test IDs** — `AuthTestIds` + `withTestIdPrefix` for E2E targeting.
|
|
108
|
+
- Re-exports `BffAuthClient`, `createFetchHttpClient` and the BFF request /
|
|
109
|
+
response types from `@dloizides/auth-client` so consumers have a single
|
|
110
|
+
import surface.
|
|
111
|
+
|
|
112
|
+
### Notes
|
|
113
|
+
|
|
114
|
+
- Strictly thin: UI + a same-origin `/bff/*` fetch client + the router helper.
|
|
115
|
+
**No secrets, no token handling, no Keycloak calls** — the per-app BFF owns
|
|
116
|
+
all of that server-side.
|
|
117
|
+
- Built on `@dloizides/auth-client` (peer dependency `>=3.0.0`), which ships
|
|
118
|
+
`BffAuthClient`.
|
|
119
|
+
- This release creates the package only. Porting `apps/katalogos-web` onto it
|
|
120
|
+
is Phase 1c.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dloizides
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# @dloizides/auth-web
|
|
2
|
+
|
|
3
|
+
Themeable, branded auth UI for the dloizides.com portfolio — the frontend half
|
|
4
|
+
of the unified-auth plan.
|
|
5
|
+
|
|
6
|
+
Every app gets a **native, branded login experience**: the login / forgot /
|
|
7
|
+
reset forms live inside the app's own frontend; the user is **never redirected
|
|
8
|
+
to Keycloak's hosted login UI**. All credential exchange happens server-side in
|
|
9
|
+
a per-app **BFF** (`bff-katalogos`, `bff-erevna`, ...). This package talks only
|
|
10
|
+
to a same-origin `/bff/*` — **no secrets, no token handling, no Keycloak calls
|
|
11
|
+
in the browser.**
|
|
12
|
+
|
|
13
|
+
Built on [`@dloizides/auth-client`](https://www.npmjs.com/package/@dloizides/auth-client),
|
|
14
|
+
which provides the lower-level `BffAuthClient`.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @dloizides/auth-web
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Peer dependencies: `react`, `react-native` (optional), `@tanstack/react-query`,
|
|
23
|
+
`@dloizides/auth-client` (`>=3.0.0`).
|
|
24
|
+
|
|
25
|
+
## Two ways to consume it
|
|
26
|
+
|
|
27
|
+
### 1. Ready-made themeable components
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import {
|
|
31
|
+
AuthThemeProvider,
|
|
32
|
+
LoginForm,
|
|
33
|
+
createBffAuthClient,
|
|
34
|
+
resolvePostLoginRoute,
|
|
35
|
+
} from '@dloizides/auth-web';
|
|
36
|
+
|
|
37
|
+
import { katalogosAuthTheme } from './theme'; // your AuthTheme token bag
|
|
38
|
+
import { roleRoutes } from './roleRoutes'; // your RoleRouteTable
|
|
39
|
+
import { authLabels } from './authLabels'; // your localised labels
|
|
40
|
+
|
|
41
|
+
const client = createBffAuthClient(); // same-origin /bff/*
|
|
42
|
+
|
|
43
|
+
function LoginScreen() {
|
|
44
|
+
const router = useRouter();
|
|
45
|
+
return (
|
|
46
|
+
<AuthThemeProvider theme={katalogosAuthTheme}>
|
|
47
|
+
<LoginForm
|
|
48
|
+
client={client}
|
|
49
|
+
labels={authLabels.login}
|
|
50
|
+
onForgotPassword={() => router.push('/forgot-password')}
|
|
51
|
+
onSuccess={(user) => {
|
|
52
|
+
const route = resolvePostLoginRoute(user, roleRoutes);
|
|
53
|
+
router.replace(route ?? '/no-access');
|
|
54
|
+
}}
|
|
55
|
+
/>
|
|
56
|
+
</AuthThemeProvider>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`<ForgotPasswordForm>` and `<ResetPasswordForm>` follow the same shape.
|
|
62
|
+
|
|
63
|
+
### Email-OTP login — `<OtpForm>`
|
|
64
|
+
|
|
65
|
+
A native, branded "sign in with a code" surface — the user is never bounced to
|
|
66
|
+
Keycloak's hosted UI. It is a two-step form: step 1 collects the email and asks
|
|
67
|
+
the BFF to email a one-time code; step 2 collects the code, verifies it, and
|
|
68
|
+
offers "resend code" / "use a different email".
|
|
69
|
+
|
|
70
|
+
```tsx
|
|
71
|
+
import { OtpForm, createBffAuthClient, resolvePostLoginRoute } from '@dloizides/auth-web';
|
|
72
|
+
|
|
73
|
+
const client = createBffAuthClient();
|
|
74
|
+
|
|
75
|
+
function OtpLoginScreen() {
|
|
76
|
+
const router = useRouter();
|
|
77
|
+
return (
|
|
78
|
+
<OtpForm
|
|
79
|
+
client={client}
|
|
80
|
+
labels={authLabels.otp}
|
|
81
|
+
onSuccess={(user) => {
|
|
82
|
+
const route = resolvePostLoginRoute(user, roleRoutes);
|
|
83
|
+
router.replace(route ?? '/no-access');
|
|
84
|
+
}}
|
|
85
|
+
/>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
`<OtpForm>` POSTs to the same-origin `/bff/otp/request` and `/bff/otp/verify`
|
|
91
|
+
endpoints (added in `Bff.AspNetCore`). The BFF runs the OTP direct-grant against
|
|
92
|
+
Keycloak server-side; the browser receives only the httpOnly session cookie.
|
|
93
|
+
|
|
94
|
+
### Event-PIN login — `<PinForm>`
|
|
95
|
+
|
|
96
|
+
A native, branded "sign in with your event PIN" surface for operational staff
|
|
97
|
+
(door / DJ / media on Kefi). It is a single-step form: a PIN field + a "sign
|
|
98
|
+
in" button. The `eventExternalId` is a prop — the event context comes from the
|
|
99
|
+
route/page, never typed by the user. The `(event, pin)` pair alone identifies
|
|
100
|
+
the staff member; no username/password ever leaves the browser.
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
import { PinForm, createBffAuthClient, resolvePostLoginRoute } from '@dloizides/auth-web';
|
|
104
|
+
|
|
105
|
+
const client = createBffAuthClient();
|
|
106
|
+
|
|
107
|
+
function PinLoginScreen({ eventExternalId }: { eventExternalId: string }) {
|
|
108
|
+
const router = useRouter();
|
|
109
|
+
return (
|
|
110
|
+
<PinForm
|
|
111
|
+
client={client}
|
|
112
|
+
eventExternalId={eventExternalId}
|
|
113
|
+
labels={authLabels.pin}
|
|
114
|
+
onSuccess={(user) => {
|
|
115
|
+
const route = resolvePostLoginRoute(user, roleRoutes);
|
|
116
|
+
router.replace(route ?? '/no-access');
|
|
117
|
+
}}
|
|
118
|
+
/>
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`<PinForm>` POSTs to the same-origin `/bff/pin/login` endpoint (added in
|
|
124
|
+
`Bff.AspNetCore`). The BFF runs the event-scoped PIN direct-grant against
|
|
125
|
+
Keycloak server-side; the browser receives only the httpOnly session cookie.
|
|
126
|
+
|
|
127
|
+
### 2. Headless hooks (custom layout)
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
import { useBffAuth, createBffAuthClient } from '@dloizides/auth-web';
|
|
131
|
+
|
|
132
|
+
const client = createBffAuthClient();
|
|
133
|
+
|
|
134
|
+
function CustomLogin() {
|
|
135
|
+
const { login, isSubmitting, error } = useBffAuth({ client, probeOnMount: false });
|
|
136
|
+
// ...render your own form, call login({ username, password })
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
For a custom OTP layout, `useOtpLogin` exposes the two-step machine:
|
|
141
|
+
|
|
142
|
+
```tsx
|
|
143
|
+
import { useOtpLogin, OtpLoginStep, createBffAuthClient } from '@dloizides/auth-web';
|
|
144
|
+
|
|
145
|
+
const client = createBffAuthClient();
|
|
146
|
+
|
|
147
|
+
function CustomOtpLogin() {
|
|
148
|
+
const otp = useOtpLogin({ client });
|
|
149
|
+
// step 1: otp.requestCode(email) → otp.step becomes OtpLoginStep.EnterCode
|
|
150
|
+
// step 2: otp.verifyCode(code) → resolves to the signed-in BffUser
|
|
151
|
+
// otp.resend() / otp.reset() for the step-2 affordances
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
For a custom PIN layout, `usePinLogin` exposes the single-step flow:
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
import { usePinLogin, createBffAuthClient } from '@dloizides/auth-web';
|
|
159
|
+
|
|
160
|
+
const client = createBffAuthClient();
|
|
161
|
+
|
|
162
|
+
function CustomPinLogin({ eventExternalId }: { eventExternalId: string }) {
|
|
163
|
+
const pin = usePinLogin({ client, eventExternalId });
|
|
164
|
+
// pin.submit(pinValue) → resolves to the signed-in BffUser
|
|
165
|
+
// pin.reset() → clears the error
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Theming
|
|
170
|
+
|
|
171
|
+
The package owns no brand. Each app maps its own theme system onto the flat
|
|
172
|
+
`AuthTheme` token bag (`colors`, `radii`, `spacing`, `typography`) and supplies
|
|
173
|
+
it via `<AuthThemeProvider>` or a `theme` prop on an individual component.
|
|
174
|
+
Precedence: **prop → context → `defaultAuthTheme`**.
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
import { defaultAuthTheme, type AuthTheme } from '@dloizides/auth-web';
|
|
178
|
+
|
|
179
|
+
export const katalogosAuthTheme: AuthTheme = {
|
|
180
|
+
...defaultAuthTheme,
|
|
181
|
+
colors: { ...defaultAuthTheme.colors, primary: '#c2410c' },
|
|
182
|
+
};
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Because all three forms share one `useAuthStyles` token-to-style mapping,
|
|
186
|
+
re-theming `<LoginForm>` automatically re-themes the others.
|
|
187
|
+
|
|
188
|
+
## Internationalisation
|
|
189
|
+
|
|
190
|
+
`@dloizides/auth-web` ships **no** i18n framework. Every user-facing string is
|
|
191
|
+
supplied through a typed `labels` prop. Apps pass strings already localised
|
|
192
|
+
with their own `FM()` / `t()`. Each label bag is partial — unspecified keys
|
|
193
|
+
fall back to the English `DEFAULT_*` constants.
|
|
194
|
+
|
|
195
|
+
## Role-based post-login routing
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
import { resolvePostLoginRoute, type RoleRouteTable } from '@dloizides/auth-web';
|
|
199
|
+
|
|
200
|
+
const roleRoutes: RoleRouteTable = {
|
|
201
|
+
routes: [
|
|
202
|
+
{ role: 'superUser', route: '/admin/super' },
|
|
203
|
+
{ role: 'admin', route: '/admin' },
|
|
204
|
+
{ role: 'user', route: '/dashboard' },
|
|
205
|
+
],
|
|
206
|
+
fallback: '/no-access',
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
// The first table entry whose role the user holds wins — list most privileged first.
|
|
210
|
+
const route = resolvePostLoginRoute(user, roleRoutes);
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## API surface
|
|
214
|
+
|
|
215
|
+
| Export | Kind |
|
|
216
|
+
|--------|------|
|
|
217
|
+
| `LoginForm`, `ForgotPasswordForm`, `ResetPasswordForm`, `OtpForm`, `PinForm` | Components |
|
|
218
|
+
| `AuthThemeProvider`, `useAuthTheme`, `defaultAuthTheme`, `AuthTheme` | Theming |
|
|
219
|
+
| `DEFAULT_LOGIN_LABELS`, `DEFAULT_FORGOT_PASSWORD_LABELS`, `DEFAULT_RESET_PASSWORD_LABELS`, `DEFAULT_OTP_LABELS`, `DEFAULT_PIN_LABELS` | Label bags |
|
|
220
|
+
| `useBffAuth`, `useBffForgotPassword`, `useBffResetPassword`, `useResetPasswordForm`, `useOtpLogin`, `usePinLogin` | Headless hooks |
|
|
221
|
+
| `OtpLoginStep` | OTP step enum |
|
|
222
|
+
| `createBffAuthClient`, `BffAuthClient` (re-export) | Client |
|
|
223
|
+
| `resolvePostLoginRoute`, `collectUserRoles`, `RoleRouteTable` | Router |
|
|
224
|
+
| `validatePasswordPolicy`, `isPasswordValid`, `PasswordPolicyError` | Password policy |
|
|
225
|
+
| `AuthTestIds`, `withTestIdPrefix` | Test IDs |
|
|
226
|
+
|
|
227
|
+
## License
|
|
228
|
+
|
|
229
|
+
MIT
|