|
|
|
|
import {
|
|
|
|
|
AUTH_CACHE_TAGS,
|
|
|
|
|
AUTH_COOKIE_NAMES,
|
|
|
|
|
AUTH_DEFAULTS,
|
|
|
|
|
AUTH_EVENT_NAMES,
|
|
|
|
|
AUTH_ROUTE_PATHS,
|
|
|
|
|
AUTH_TEST_ACTIONS,
|
|
|
|
|
AUTH_TEST_COOKIE_NAMES,
|
|
|
|
|
AUTH_TEST_FORM_FIELDS,
|
|
|
|
|
AUTH_TEST_IDS,
|
|
|
|
|
AUTH_TEST_TENANT_ID,
|
|
|
|
|
issueAuthCsrf,
|
|
|
|
|
verifyAuthCsrf,
|
|
|
|
|
type AuthSessionId
|
|
|
|
|
} from '$libs/auth';
|
|
|
|
|
import {
|
|
|
|
|
createDeterministicAuthCrypto,
|
|
|
|
|
createEngineAuth,
|
|
|
|
|
createMemoryAuthActors,
|
|
|
|
|
createMemoryAuthAdapter,
|
Rename modules from 4-letter aliases to full English words
Drops the 4-letter alias convention in favour of a single homogeneous
naming axis: full English words across filesystem, alias, wire format
and constants.
Module renames:
- arts/aapp → arts/active-app (libs/aapp also)
- arts/buss → arts/bus (libs/buss also)
- arts/cach → arts/cache (libs/cach + svrs/cach also)
- arts/conn → arts/connection
- arts/fend → arts/frontend
- arts/fmts → arts/formats (curr→currency, nums→numbers, unts→units)
- arts/logr → arts/logger (libs/logr also)
- arts/perm → arts/permissions (libs/perm + svrs/perm also)
- arts/sess → arts/session
- arts/stor → arts/storage
- arts/timr → arts/timer (libs/timers → libs/timer)
Modules left as-is: auth, dom, errs, http, lang, sium (already match
their canonical name or are proper names).
Special case: `aapp` could not become `app` because `$app` is reserved
by SvelteKit (`$app/stores`, `$app/navigation`, ...). Compromise:
- Filesystem and alias use `active-app` / `$active-app`.
- Constants and class names use `App` / `APP_*` (no `active-` prefix).
The `active-` prefix only disambiguates the alias from SvelteKit's
namespace; the module is App.
Special case: `permissions` keeps the plural for filesystem/alias/wire
but constants and classes use the singular `PERMISSION_*` /
`Permission*` because they describe the concept ("a permission
effect"), not the module collection.
Constants follow the new module name in caps: `STORAGE_*`, `BUS_*`,
`CACHE_*`, `CONNECTION_*`, `FORMATS_*`, `LOGGER_*`, `SESSION_*`,
`TIMER_*`, etc. Module values: `STORAGE_MODULE = 'storage'`,
`BUS_MODULE = 'bus'`, `APP_MODULE = 'app'`,
`PERMISSION_MODULE = 'permissions'`, etc.
Wire/code format moved accordingly: `'storage::*'`, `'bus::*'`,
`'session::*'`, `'permissions::*'`, etc. Diagnostic event values
updated: `'storage.error'`, `'bus.event.published'`,
`'connection.auth_failed'`, etc. App events use `'app.*'`:
`AAPP_EVENT_* → APP_EVENT_*` with values `'app.user.identity.changed'`.
Class renames (where they used the abbreviation):
- AappAlreadyCreatedError → AppAlreadyCreatedError
- BussError* → BusError* (where applicable)
- Cach* → Cache*
- Conn* → Connection* (e.g. ConnDisposedError → ConnectionDisposedError;
ConnConnection* collapsed to Connection*)
- Logr*Error → Logger*Error
- Sess* → Session* (SessInvalidSessionError → SessionInvalidError)
- Stor* → Storage*
- Timr* → Timer* (TimrInactiveTimerError → TimerInactiveError)
- AuthCachPort → AuthCachePort
- AuthClientCach* → AuthClientCache*
- AuthPermPort → AuthPermissionsPort
Property renames in option types:
- `cach?:` → `cache?:` in AuthClient options
- `logr:` → `logger:` in svrs/auth ports
- `timr:` → `timer:` in svrs/auth ports
`docs/conventions.md` rewritten:
- Rule 1 dropped the 4-letter alias mandate; lists the full English
module names and special-cases active-app, lang, sium, permissions.
- Rule 2 documents the new constant prefix convention and its two
exceptions (APP_* for active-app, PERMISSION_* singular for
permissions).
- Rule 6 codifies that all error infrastructure (codes, messages,
classes, guards) lives in a single `errors.ts` per module —
removing the `consts.ts` / `errors.ts` split for error-related
symbols.
`libs/errs` adds `ErrorMessages` type so every module can declare its
catalog as `<MOD>_ERROR_MESSAGES: ErrorMessages` instead of repeating
the `Readonly<Record<ErrCode, string | (...args) => string>>` shape.
Storage migrated as the first proof of the canonical pattern.
All 1334 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
createMemoryAuthCache,
|
|
|
|
|
createMemoryAuthClock,
|
|
|
|
|
createMemoryAuthLogr,
|
|
|
|
|
createMemoryAuthMailer,
|
|
|
|
|
createMemoryAuthSessPort,
|
|
|
|
|
createTestPasswordHasher
|
|
|
|
|
} from '$svrs/auth';
|
|
|
|
|
|
|
|
|
|
export const prerender = false;
|
|
|
|
|
|
|
|
|
|
const AUTH_TEST_PASSWORD = 'correct horse battery staple';
|
|
|
|
|
const AUTH_TEST_CSRF_KEY = 'test-page-csrf-key';
|
|
|
|
|
const AUTH_TEST_SESSION_COOKIE_MAX_AGE = 60 * 60;
|
|
|
|
|
|
|
|
|
|
let harness = createHarness();
|
|
|
|
|
|
|
|
|
|
export async function load({ cookies }: { cookies: { get(name: string): string | undefined } }) {
|
|
|
|
|
const sessionId = cookies.get(AUTH_TEST_COOKIE_NAMES.SESSION) as AuthSessionId | undefined;
|
|
|
|
|
return {
|
|
|
|
|
auth: {
|
|
|
|
|
routes: AUTH_ROUTE_PATHS,
|
|
|
|
|
events: AUTH_EVENT_NAMES,
|
|
|
|
|
cacheTags: AUTH_CACHE_TAGS,
|
|
|
|
|
testIds: AUTH_TEST_IDS,
|
|
|
|
|
actions: AUTH_TEST_ACTIONS,
|
|
|
|
|
fields: AUTH_TEST_FORM_FIELDS,
|
|
|
|
|
defaults: {
|
|
|
|
|
identifier: 'ada@example.com',
|
|
|
|
|
password: AUTH_TEST_PASSWORD,
|
|
|
|
|
displayName: 'Ada Lovelace'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
state: await snapshot(sessionId)
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const actions = {
|
|
|
|
|
async signUp({ request, cookies }: TestActionEvent) {
|
|
|
|
|
return runAuthAction(cookies, AUTH_TEST_ACTIONS.SIGN_UP, async () => {
|
|
|
|
|
const input = await readPasswordForm(request);
|
|
|
|
|
const result = await harness.engine.signUpPassword({
|
|
|
|
|
tenantId: AUTH_TEST_TENANT_ID,
|
|
|
|
|
identifier: input.identifier,
|
|
|
|
|
password: input.password,
|
|
|
|
|
profile: { displayName: input.displayName },
|
|
|
|
|
device: {
|
|
|
|
|
displayName: 'Test browser',
|
|
|
|
|
userAgentDisplay: 'SvelteKit action'
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
writeSessionCookie(cookies, result.session.sessionId);
|
|
|
|
|
return { current: result.current };
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async signIn({ request, cookies }: TestActionEvent) {
|
|
|
|
|
return runAuthAction(cookies, AUTH_TEST_ACTIONS.SIGN_IN, async () => {
|
|
|
|
|
const input = await readPasswordForm(request);
|
|
|
|
|
const result = await harness.engine.signInPassword({
|
|
|
|
|
tenantId: AUTH_TEST_TENANT_ID,
|
|
|
|
|
identifier: input.identifier,
|
|
|
|
|
password: input.password,
|
|
|
|
|
device: {
|
|
|
|
|
displayName: 'Test browser',
|
|
|
|
|
userAgentDisplay: 'SvelteKit action'
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
writeSessionCookie(cookies, result.session.sessionId);
|
|
|
|
|
return { current: result.current };
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async signOut({ cookies }: TestActionEvent) {
|
|
|
|
|
return runAuthAction(cookies, AUTH_TEST_ACTIONS.SIGN_OUT, async () => {
|
|
|
|
|
const sessionId = readSessionCookie(cookies);
|
|
|
|
|
const result = await harness.engine.signOut({
|
|
|
|
|
tenantId: AUTH_TEST_TENANT_ID,
|
|
|
|
|
sessionId
|
|
|
|
|
});
|
|
|
|
|
clearSessionCookie(cookies);
|
|
|
|
|
return { current: result.current, endedSessionIds: result.endedSessionIds };
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async signOutGlobal({ cookies }: TestActionEvent) {
|
|
|
|
|
return runAuthAction(cookies, AUTH_TEST_ACTIONS.SIGN_OUT_GLOBAL, async () => {
|
|
|
|
|
const sessionId = readSessionCookie(cookies);
|
|
|
|
|
const result = await harness.engine.signOutGlobal({
|
|
|
|
|
tenantId: AUTH_TEST_TENANT_ID,
|
|
|
|
|
sessionId
|
|
|
|
|
});
|
|
|
|
|
clearSessionCookie(cookies);
|
|
|
|
|
return { current: result.current, endedSessionIds: result.endedSessionIds };
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async csrfRoundtrip({ cookies }: TestActionEvent) {
|
|
|
|
|
return runAuthAction(cookies, AUTH_TEST_ACTIONS.CSRF_ROUNDTRIP, async () => {
|
|
|
|
|
const result = await harness.engine.issueCsrf({ tenantId: AUTH_TEST_TENANT_ID });
|
|
|
|
|
await harness.engine.verifyCsrf({
|
|
|
|
|
tenantId: AUTH_TEST_TENANT_ID,
|
|
|
|
|
token: result.token,
|
|
|
|
|
cookie: result.cookie.value
|
|
|
|
|
});
|
|
|
|
|
return {
|
|
|
|
|
csrf: {
|
|
|
|
|
ok: true,
|
|
|
|
|
expiresAt: result.expiresAt,
|
|
|
|
|
cookieName: AUTH_COOKIE_NAMES.CSRF
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async csrfExpired({ cookies }: TestActionEvent) {
|
|
|
|
|
return runAuthAction(cookies, AUTH_TEST_ACTIONS.CSRF_EXPIRED, async () => {
|
|
|
|
|
const crypto = createDeterministicAuthCrypto('auth-test-page-expired-csrf');
|
|
|
|
|
const clock = createMemoryAuthClock(10_000);
|
|
|
|
|
const result = await issueAuthCsrf({
|
|
|
|
|
crypto,
|
|
|
|
|
clock,
|
|
|
|
|
tenantId: AUTH_TEST_TENANT_ID,
|
|
|
|
|
config: { signingKey: AUTH_TEST_CSRF_KEY }
|
|
|
|
|
});
|
|
|
|
|
clock.advance(AUTH_DEFAULTS.CSRF_TTL_MS + 1);
|
|
|
|
|
try {
|
|
|
|
|
await verifyAuthCsrf({
|
|
|
|
|
crypto,
|
|
|
|
|
clock,
|
|
|
|
|
tenantId: AUTH_TEST_TENANT_ID,
|
|
|
|
|
token: result.token,
|
|
|
|
|
cookie: result.cookie.value,
|
|
|
|
|
config: { signingKey: AUTH_TEST_CSRF_KEY }
|
|
|
|
|
});
|
|
|
|
|
return { csrf: { ok: false, reason: 'unexpected-valid-token' } };
|
|
|
|
|
} catch (error) {
|
|
|
|
|
return { csrf: { ok: true, rejected: errorName(error) } };
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
async reset({ cookies }: TestActionEvent) {
|
|
|
|
|
harness = createHarness();
|
|
|
|
|
clearSessionCookie(cookies);
|
|
|
|
|
return {
|
|
|
|
|
ok: true,
|
|
|
|
|
action: AUTH_TEST_ACTIONS.RESET,
|
|
|
|
|
message: 'Auth test harness reset.',
|
|
|
|
|
state: await snapshot()
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
interface TestCookies {
|
|
|
|
|
get(name: string): string | undefined;
|
|
|
|
|
set(name: string, value: string, options: TestCookieOptions): void;
|
|
|
|
|
delete(name: string, options: Pick<TestCookieOptions, 'path'>): void;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface TestCookieOptions {
|
|
|
|
|
readonly path: string;
|
|
|
|
|
readonly httpOnly?: boolean;
|
|
|
|
|
readonly sameSite?: 'strict' | 'lax' | 'none';
|
|
|
|
|
readonly secure?: boolean;
|
|
|
|
|
readonly maxAge?: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface TestActionEvent {
|
|
|
|
|
readonly request: Request;
|
|
|
|
|
readonly cookies: TestCookies;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface PasswordFormInput {
|
|
|
|
|
readonly identifier: string;
|
|
|
|
|
readonly password: string;
|
|
|
|
|
readonly displayName: string;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function createHarness() {
|
|
|
|
|
const crypto = createDeterministicAuthCrypto('auth-test-page');
|
|
|
|
|
const clock = createMemoryAuthClock(Date.now());
|
|
|
|
|
const store = createMemoryAuthAdapter({ suppressProductionWarning: true });
|
|
|
|
|
const actors = createMemoryAuthActors(crypto);
|
|
|
|
|
const sess = createMemoryAuthSessPort({ crypto, clock });
|
|
|
|
|
const logr = createMemoryAuthLogr();
|
Rename modules from 4-letter aliases to full English words
Drops the 4-letter alias convention in favour of a single homogeneous
naming axis: full English words across filesystem, alias, wire format
and constants.
Module renames:
- arts/aapp → arts/active-app (libs/aapp also)
- arts/buss → arts/bus (libs/buss also)
- arts/cach → arts/cache (libs/cach + svrs/cach also)
- arts/conn → arts/connection
- arts/fend → arts/frontend
- arts/fmts → arts/formats (curr→currency, nums→numbers, unts→units)
- arts/logr → arts/logger (libs/logr also)
- arts/perm → arts/permissions (libs/perm + svrs/perm also)
- arts/sess → arts/session
- arts/stor → arts/storage
- arts/timr → arts/timer (libs/timers → libs/timer)
Modules left as-is: auth, dom, errs, http, lang, sium (already match
their canonical name or are proper names).
Special case: `aapp` could not become `app` because `$app` is reserved
by SvelteKit (`$app/stores`, `$app/navigation`, ...). Compromise:
- Filesystem and alias use `active-app` / `$active-app`.
- Constants and class names use `App` / `APP_*` (no `active-` prefix).
The `active-` prefix only disambiguates the alias from SvelteKit's
namespace; the module is App.
Special case: `permissions` keeps the plural for filesystem/alias/wire
but constants and classes use the singular `PERMISSION_*` /
`Permission*` because they describe the concept ("a permission
effect"), not the module collection.
Constants follow the new module name in caps: `STORAGE_*`, `BUS_*`,
`CACHE_*`, `CONNECTION_*`, `FORMATS_*`, `LOGGER_*`, `SESSION_*`,
`TIMER_*`, etc. Module values: `STORAGE_MODULE = 'storage'`,
`BUS_MODULE = 'bus'`, `APP_MODULE = 'app'`,
`PERMISSION_MODULE = 'permissions'`, etc.
Wire/code format moved accordingly: `'storage::*'`, `'bus::*'`,
`'session::*'`, `'permissions::*'`, etc. Diagnostic event values
updated: `'storage.error'`, `'bus.event.published'`,
`'connection.auth_failed'`, etc. App events use `'app.*'`:
`AAPP_EVENT_* → APP_EVENT_*` with values `'app.user.identity.changed'`.
Class renames (where they used the abbreviation):
- AappAlreadyCreatedError → AppAlreadyCreatedError
- BussError* → BusError* (where applicable)
- Cach* → Cache*
- Conn* → Connection* (e.g. ConnDisposedError → ConnectionDisposedError;
ConnConnection* collapsed to Connection*)
- Logr*Error → Logger*Error
- Sess* → Session* (SessInvalidSessionError → SessionInvalidError)
- Stor* → Storage*
- Timr* → Timer* (TimrInactiveTimerError → TimerInactiveError)
- AuthCachPort → AuthCachePort
- AuthClientCach* → AuthClientCache*
- AuthPermPort → AuthPermissionsPort
Property renames in option types:
- `cach?:` → `cache?:` in AuthClient options
- `logr:` → `logger:` in svrs/auth ports
- `timr:` → `timer:` in svrs/auth ports
`docs/conventions.md` rewritten:
- Rule 1 dropped the 4-letter alias mandate; lists the full English
module names and special-cases active-app, lang, sium, permissions.
- Rule 2 documents the new constant prefix convention and its two
exceptions (APP_* for active-app, PERMISSION_* singular for
permissions).
- Rule 6 codifies that all error infrastructure (codes, messages,
classes, guards) lives in a single `errors.ts` per module —
removing the `consts.ts` / `errors.ts` split for error-related
symbols.
`libs/errs` adds `ErrorMessages` type so every module can declare its
catalog as `<MOD>_ERROR_MESSAGES: ErrorMessages` instead of repeating
the `Readonly<Record<ErrCode, string | (...args) => string>>` shape.
Storage migrated as the first proof of the canonical pattern.
All 1334 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
const cach = createMemoryAuthCache();
|
|
|
|
|
const mailer = createMemoryAuthMailer();
|
|
|
|
|
const engine = createEngineAuth({
|
|
|
|
|
security: { csrf: { signingKey: AUTH_TEST_CSRF_KEY } },
|
|
|
|
|
ports: {
|
|
|
|
|
store,
|
|
|
|
|
actors,
|
|
|
|
|
sess,
|
Rename modules from 4-letter aliases to full English words
Drops the 4-letter alias convention in favour of a single homogeneous
naming axis: full English words across filesystem, alias, wire format
and constants.
Module renames:
- arts/aapp → arts/active-app (libs/aapp also)
- arts/buss → arts/bus (libs/buss also)
- arts/cach → arts/cache (libs/cach + svrs/cach also)
- arts/conn → arts/connection
- arts/fend → arts/frontend
- arts/fmts → arts/formats (curr→currency, nums→numbers, unts→units)
- arts/logr → arts/logger (libs/logr also)
- arts/perm → arts/permissions (libs/perm + svrs/perm also)
- arts/sess → arts/session
- arts/stor → arts/storage
- arts/timr → arts/timer (libs/timers → libs/timer)
Modules left as-is: auth, dom, errs, http, lang, sium (already match
their canonical name or are proper names).
Special case: `aapp` could not become `app` because `$app` is reserved
by SvelteKit (`$app/stores`, `$app/navigation`, ...). Compromise:
- Filesystem and alias use `active-app` / `$active-app`.
- Constants and class names use `App` / `APP_*` (no `active-` prefix).
The `active-` prefix only disambiguates the alias from SvelteKit's
namespace; the module is App.
Special case: `permissions` keeps the plural for filesystem/alias/wire
but constants and classes use the singular `PERMISSION_*` /
`Permission*` because they describe the concept ("a permission
effect"), not the module collection.
Constants follow the new module name in caps: `STORAGE_*`, `BUS_*`,
`CACHE_*`, `CONNECTION_*`, `FORMATS_*`, `LOGGER_*`, `SESSION_*`,
`TIMER_*`, etc. Module values: `STORAGE_MODULE = 'storage'`,
`BUS_MODULE = 'bus'`, `APP_MODULE = 'app'`,
`PERMISSION_MODULE = 'permissions'`, etc.
Wire/code format moved accordingly: `'storage::*'`, `'bus::*'`,
`'session::*'`, `'permissions::*'`, etc. Diagnostic event values
updated: `'storage.error'`, `'bus.event.published'`,
`'connection.auth_failed'`, etc. App events use `'app.*'`:
`AAPP_EVENT_* → APP_EVENT_*` with values `'app.user.identity.changed'`.
Class renames (where they used the abbreviation):
- AappAlreadyCreatedError → AppAlreadyCreatedError
- BussError* → BusError* (where applicable)
- Cach* → Cache*
- Conn* → Connection* (e.g. ConnDisposedError → ConnectionDisposedError;
ConnConnection* collapsed to Connection*)
- Logr*Error → Logger*Error
- Sess* → Session* (SessInvalidSessionError → SessionInvalidError)
- Stor* → Storage*
- Timr* → Timer* (TimrInactiveTimerError → TimerInactiveError)
- AuthCachPort → AuthCachePort
- AuthClientCach* → AuthClientCache*
- AuthPermPort → AuthPermissionsPort
Property renames in option types:
- `cach?:` → `cache?:` in AuthClient options
- `logr:` → `logger:` in svrs/auth ports
- `timr:` → `timer:` in svrs/auth ports
`docs/conventions.md` rewritten:
- Rule 1 dropped the 4-letter alias mandate; lists the full English
module names and special-cases active-app, lang, sium, permissions.
- Rule 2 documents the new constant prefix convention and its two
exceptions (APP_* for active-app, PERMISSION_* singular for
permissions).
- Rule 6 codifies that all error infrastructure (codes, messages,
classes, guards) lives in a single `errors.ts` per module —
removing the `consts.ts` / `errors.ts` split for error-related
symbols.
`libs/errs` adds `ErrorMessages` type so every module can declare its
catalog as `<MOD>_ERROR_MESSAGES: ErrorMessages` instead of repeating
the `Readonly<Record<ErrCode, string | (...args) => string>>` shape.
Storage migrated as the first proof of the canonical pattern.
All 1334 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
logger,
|
|
|
|
|
timer: clock,
|
|
|
|
|
crypto,
|
|
|
|
|
cach,
|
|
|
|
|
mailer,
|
|
|
|
|
passwordHasher: createTestPasswordHasher()
|
|
|
|
|
}
|
|
|
|
|
});
|
Rename modules from 4-letter aliases to full English words
Drops the 4-letter alias convention in favour of a single homogeneous
naming axis: full English words across filesystem, alias, wire format
and constants.
Module renames:
- arts/aapp → arts/active-app (libs/aapp also)
- arts/buss → arts/bus (libs/buss also)
- arts/cach → arts/cache (libs/cach + svrs/cach also)
- arts/conn → arts/connection
- arts/fend → arts/frontend
- arts/fmts → arts/formats (curr→currency, nums→numbers, unts→units)
- arts/logr → arts/logger (libs/logr also)
- arts/perm → arts/permissions (libs/perm + svrs/perm also)
- arts/sess → arts/session
- arts/stor → arts/storage
- arts/timr → arts/timer (libs/timers → libs/timer)
Modules left as-is: auth, dom, errs, http, lang, sium (already match
their canonical name or are proper names).
Special case: `aapp` could not become `app` because `$app` is reserved
by SvelteKit (`$app/stores`, `$app/navigation`, ...). Compromise:
- Filesystem and alias use `active-app` / `$active-app`.
- Constants and class names use `App` / `APP_*` (no `active-` prefix).
The `active-` prefix only disambiguates the alias from SvelteKit's
namespace; the module is App.
Special case: `permissions` keeps the plural for filesystem/alias/wire
but constants and classes use the singular `PERMISSION_*` /
`Permission*` because they describe the concept ("a permission
effect"), not the module collection.
Constants follow the new module name in caps: `STORAGE_*`, `BUS_*`,
`CACHE_*`, `CONNECTION_*`, `FORMATS_*`, `LOGGER_*`, `SESSION_*`,
`TIMER_*`, etc. Module values: `STORAGE_MODULE = 'storage'`,
`BUS_MODULE = 'bus'`, `APP_MODULE = 'app'`,
`PERMISSION_MODULE = 'permissions'`, etc.
Wire/code format moved accordingly: `'storage::*'`, `'bus::*'`,
`'session::*'`, `'permissions::*'`, etc. Diagnostic event values
updated: `'storage.error'`, `'bus.event.published'`,
`'connection.auth_failed'`, etc. App events use `'app.*'`:
`AAPP_EVENT_* → APP_EVENT_*` with values `'app.user.identity.changed'`.
Class renames (where they used the abbreviation):
- AappAlreadyCreatedError → AppAlreadyCreatedError
- BussError* → BusError* (where applicable)
- Cach* → Cache*
- Conn* → Connection* (e.g. ConnDisposedError → ConnectionDisposedError;
ConnConnection* collapsed to Connection*)
- Logr*Error → Logger*Error
- Sess* → Session* (SessInvalidSessionError → SessionInvalidError)
- Stor* → Storage*
- Timr* → Timer* (TimrInactiveTimerError → TimerInactiveError)
- AuthCachPort → AuthCachePort
- AuthClientCach* → AuthClientCache*
- AuthPermPort → AuthPermissionsPort
Property renames in option types:
- `cach?:` → `cache?:` in AuthClient options
- `logr:` → `logger:` in svrs/auth ports
- `timr:` → `timer:` in svrs/auth ports
`docs/conventions.md` rewritten:
- Rule 1 dropped the 4-letter alias mandate; lists the full English
module names and special-cases active-app, lang, sium, permissions.
- Rule 2 documents the new constant prefix convention and its two
exceptions (APP_* for active-app, PERMISSION_* singular for
permissions).
- Rule 6 codifies that all error infrastructure (codes, messages,
classes, guards) lives in a single `errors.ts` per module —
removing the `consts.ts` / `errors.ts` split for error-related
symbols.
`libs/errs` adds `ErrorMessages` type so every module can declare its
catalog as `<MOD>_ERROR_MESSAGES: ErrorMessages` instead of repeating
the `Readonly<Record<ErrCode, string | (...args) => string>>` shape.
Storage migrated as the first proof of the canonical pattern.
All 1334 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
return { engine, store, actors, sess, logger, cach, mailer, clock };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function snapshot(sessionId?: AuthSessionId) {
|
|
|
|
|
const current = await harness.engine.current({
|
|
|
|
|
tenantId: AUTH_TEST_TENANT_ID,
|
|
|
|
|
sessionId
|
|
|
|
|
});
|
|
|
|
|
const store = harness.store.snapshot();
|
|
|
|
|
return {
|
|
|
|
|
current,
|
|
|
|
|
counts: {
|
|
|
|
|
actors: harness.actors.snapshot().length,
|
|
|
|
|
credentials: store.credentials.length,
|
|
|
|
|
sessions: store.sessionBindings.length,
|
|
|
|
|
devices: store.devices.length,
|
|
|
|
|
flows: store.flows.length,
|
|
|
|
|
events: harness.logr.entries.length,
|
Rename modules from 4-letter aliases to full English words
Drops the 4-letter alias convention in favour of a single homogeneous
naming axis: full English words across filesystem, alias, wire format
and constants.
Module renames:
- arts/aapp → arts/active-app (libs/aapp also)
- arts/buss → arts/bus (libs/buss also)
- arts/cach → arts/cache (libs/cach + svrs/cach also)
- arts/conn → arts/connection
- arts/fend → arts/frontend
- arts/fmts → arts/formats (curr→currency, nums→numbers, unts→units)
- arts/logr → arts/logger (libs/logr also)
- arts/perm → arts/permissions (libs/perm + svrs/perm also)
- arts/sess → arts/session
- arts/stor → arts/storage
- arts/timr → arts/timer (libs/timers → libs/timer)
Modules left as-is: auth, dom, errs, http, lang, sium (already match
their canonical name or are proper names).
Special case: `aapp` could not become `app` because `$app` is reserved
by SvelteKit (`$app/stores`, `$app/navigation`, ...). Compromise:
- Filesystem and alias use `active-app` / `$active-app`.
- Constants and class names use `App` / `APP_*` (no `active-` prefix).
The `active-` prefix only disambiguates the alias from SvelteKit's
namespace; the module is App.
Special case: `permissions` keeps the plural for filesystem/alias/wire
but constants and classes use the singular `PERMISSION_*` /
`Permission*` because they describe the concept ("a permission
effect"), not the module collection.
Constants follow the new module name in caps: `STORAGE_*`, `BUS_*`,
`CACHE_*`, `CONNECTION_*`, `FORMATS_*`, `LOGGER_*`, `SESSION_*`,
`TIMER_*`, etc. Module values: `STORAGE_MODULE = 'storage'`,
`BUS_MODULE = 'bus'`, `APP_MODULE = 'app'`,
`PERMISSION_MODULE = 'permissions'`, etc.
Wire/code format moved accordingly: `'storage::*'`, `'bus::*'`,
`'session::*'`, `'permissions::*'`, etc. Diagnostic event values
updated: `'storage.error'`, `'bus.event.published'`,
`'connection.auth_failed'`, etc. App events use `'app.*'`:
`AAPP_EVENT_* → APP_EVENT_*` with values `'app.user.identity.changed'`.
Class renames (where they used the abbreviation):
- AappAlreadyCreatedError → AppAlreadyCreatedError
- BussError* → BusError* (where applicable)
- Cach* → Cache*
- Conn* → Connection* (e.g. ConnDisposedError → ConnectionDisposedError;
ConnConnection* collapsed to Connection*)
- Logr*Error → Logger*Error
- Sess* → Session* (SessInvalidSessionError → SessionInvalidError)
- Stor* → Storage*
- Timr* → Timer* (TimrInactiveTimerError → TimerInactiveError)
- AuthCachPort → AuthCachePort
- AuthClientCach* → AuthClientCache*
- AuthPermPort → AuthPermissionsPort
Property renames in option types:
- `cach?:` → `cache?:` in AuthClient options
- `logr:` → `logger:` in svrs/auth ports
- `timr:` → `timer:` in svrs/auth ports
`docs/conventions.md` rewritten:
- Rule 1 dropped the 4-letter alias mandate; lists the full English
module names and special-cases active-app, lang, sium, permissions.
- Rule 2 documents the new constant prefix convention and its two
exceptions (APP_* for active-app, PERMISSION_* singular for
permissions).
- Rule 6 codifies that all error infrastructure (codes, messages,
classes, guards) lives in a single `errors.ts` per module —
removing the `consts.ts` / `errors.ts` split for error-related
symbols.
`libs/errs` adds `ErrorMessages` type so every module can declare its
catalog as `<MOD>_ERROR_MESSAGES: ErrorMessages` instead of repeating
the `Readonly<Record<ErrCode, string | (...args) => string>>` shape.
Storage migrated as the first proof of the canonical pattern.
All 1334 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
cacheInvalidations: harness.cache.invalidations.length,
|
|
|
|
|
mails: harness.mailer.messages.length
|
|
|
|
|
},
|
|
|
|
|
events: harness.logr.entries.slice(-10).map((entry) => ({
|
|
|
|
|
level: entry.level,
|
|
|
|
|
category: entry.category,
|
|
|
|
|
message: entry.message,
|
|
|
|
|
eventName: entry.eventName,
|
|
|
|
|
code: entry.code,
|
|
|
|
|
actorId: entry.actorRef?.actorId,
|
|
|
|
|
sessionId: entry.sessionId
|
|
|
|
|
})),
|
Rename modules from 4-letter aliases to full English words
Drops the 4-letter alias convention in favour of a single homogeneous
naming axis: full English words across filesystem, alias, wire format
and constants.
Module renames:
- arts/aapp → arts/active-app (libs/aapp also)
- arts/buss → arts/bus (libs/buss also)
- arts/cach → arts/cache (libs/cach + svrs/cach also)
- arts/conn → arts/connection
- arts/fend → arts/frontend
- arts/fmts → arts/formats (curr→currency, nums→numbers, unts→units)
- arts/logr → arts/logger (libs/logr also)
- arts/perm → arts/permissions (libs/perm + svrs/perm also)
- arts/sess → arts/session
- arts/stor → arts/storage
- arts/timr → arts/timer (libs/timers → libs/timer)
Modules left as-is: auth, dom, errs, http, lang, sium (already match
their canonical name or are proper names).
Special case: `aapp` could not become `app` because `$app` is reserved
by SvelteKit (`$app/stores`, `$app/navigation`, ...). Compromise:
- Filesystem and alias use `active-app` / `$active-app`.
- Constants and class names use `App` / `APP_*` (no `active-` prefix).
The `active-` prefix only disambiguates the alias from SvelteKit's
namespace; the module is App.
Special case: `permissions` keeps the plural for filesystem/alias/wire
but constants and classes use the singular `PERMISSION_*` /
`Permission*` because they describe the concept ("a permission
effect"), not the module collection.
Constants follow the new module name in caps: `STORAGE_*`, `BUS_*`,
`CACHE_*`, `CONNECTION_*`, `FORMATS_*`, `LOGGER_*`, `SESSION_*`,
`TIMER_*`, etc. Module values: `STORAGE_MODULE = 'storage'`,
`BUS_MODULE = 'bus'`, `APP_MODULE = 'app'`,
`PERMISSION_MODULE = 'permissions'`, etc.
Wire/code format moved accordingly: `'storage::*'`, `'bus::*'`,
`'session::*'`, `'permissions::*'`, etc. Diagnostic event values
updated: `'storage.error'`, `'bus.event.published'`,
`'connection.auth_failed'`, etc. App events use `'app.*'`:
`AAPP_EVENT_* → APP_EVENT_*` with values `'app.user.identity.changed'`.
Class renames (where they used the abbreviation):
- AappAlreadyCreatedError → AppAlreadyCreatedError
- BussError* → BusError* (where applicable)
- Cach* → Cache*
- Conn* → Connection* (e.g. ConnDisposedError → ConnectionDisposedError;
ConnConnection* collapsed to Connection*)
- Logr*Error → Logger*Error
- Sess* → Session* (SessInvalidSessionError → SessionInvalidError)
- Stor* → Storage*
- Timr* → Timer* (TimrInactiveTimerError → TimerInactiveError)
- AuthCachPort → AuthCachePort
- AuthClientCach* → AuthClientCache*
- AuthPermPort → AuthPermissionsPort
Property renames in option types:
- `cach?:` → `cache?:` in AuthClient options
- `logr:` → `logger:` in svrs/auth ports
- `timr:` → `timer:` in svrs/auth ports
`docs/conventions.md` rewritten:
- Rule 1 dropped the 4-letter alias mandate; lists the full English
module names and special-cases active-app, lang, sium, permissions.
- Rule 2 documents the new constant prefix convention and its two
exceptions (APP_* for active-app, PERMISSION_* singular for
permissions).
- Rule 6 codifies that all error infrastructure (codes, messages,
classes, guards) lives in a single `errors.ts` per module —
removing the `consts.ts` / `errors.ts` split for error-related
symbols.
`libs/errs` adds `ErrorMessages` type so every module can declare its
catalog as `<MOD>_ERROR_MESSAGES: ErrorMessages` instead of repeating
the `Readonly<Record<ErrCode, string | (...args) => string>>` shape.
Storage migrated as the first proof of the canonical pattern.
All 1334 tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 months ago
|
|
|
cacheInvalidations: harness.cache.invalidations.slice(-10),
|
|
|
|
|
devices: store.devices.slice(-10),
|
|
|
|
|
credentials: store.credentials.map((credential) => ({
|
|
|
|
|
id: credential.id,
|
|
|
|
|
kind: credential.kind,
|
|
|
|
|
identifierDisplay: credential.identifierDisplay,
|
|
|
|
|
actorId: credential.actorRef.actorId,
|
|
|
|
|
verifiedAt: credential.verifiedAt
|
|
|
|
|
}))
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function runAuthAction(
|
|
|
|
|
cookies: TestCookies,
|
|
|
|
|
action: string,
|
|
|
|
|
run: () => Promise<Record<string, unknown>>
|
|
|
|
|
) {
|
|
|
|
|
try {
|
|
|
|
|
const result = await run();
|
|
|
|
|
return {
|
|
|
|
|
ok: true,
|
|
|
|
|
action,
|
|
|
|
|
...result,
|
|
|
|
|
state: await snapshot(readSessionCookie(cookies))
|
|
|
|
|
};
|
|
|
|
|
} catch (error) {
|
|
|
|
|
return {
|
|
|
|
|
ok: false,
|
|
|
|
|
action,
|
|
|
|
|
error: errorToPayload(error),
|
|
|
|
|
state: await snapshot(readSessionCookie(cookies))
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function readPasswordForm(request: Request): Promise<PasswordFormInput> {
|
|
|
|
|
const form = await request.formData();
|
|
|
|
|
return {
|
|
|
|
|
identifier: String(form.get(AUTH_TEST_FORM_FIELDS.IDENTIFIER) ?? ''),
|
|
|
|
|
password: String(form.get(AUTH_TEST_FORM_FIELDS.PASSWORD) ?? ''),
|
|
|
|
|
displayName: String(form.get(AUTH_TEST_FORM_FIELDS.DISPLAY_NAME) ?? '')
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function readSessionCookie(cookies: TestCookies): AuthSessionId | undefined {
|
|
|
|
|
return cookies.get(AUTH_TEST_COOKIE_NAMES.SESSION) as AuthSessionId | undefined;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function writeSessionCookie(cookies: TestCookies, sessionId: AuthSessionId | undefined): void {
|
|
|
|
|
if (!sessionId) return;
|
|
|
|
|
cookies.set(AUTH_TEST_COOKIE_NAMES.SESSION, sessionId, {
|
|
|
|
|
path: '/test/auth',
|
|
|
|
|
httpOnly: true,
|
|
|
|
|
sameSite: 'lax',
|
|
|
|
|
secure: false,
|
|
|
|
|
maxAge: AUTH_TEST_SESSION_COOKIE_MAX_AGE
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function clearSessionCookie(cookies: TestCookies): void {
|
|
|
|
|
cookies.delete(AUTH_TEST_COOKIE_NAMES.SESSION, { path: '/test/auth' });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function errorToPayload(error: unknown) {
|
|
|
|
|
return {
|
|
|
|
|
name: errorName(error),
|
|
|
|
|
message:
|
|
|
|
|
error instanceof Error
|
|
|
|
|
? error.message
|
|
|
|
|
: typeof error === 'string'
|
|
|
|
|
? error
|
|
|
|
|
: 'Unknown auth error',
|
|
|
|
|
code: typeof error === 'object' && error && 'code' in error ? String(error.code) : undefined
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function errorName(error: unknown): string {
|
|
|
|
|
return error instanceof Error ? error.name : typeof error;
|
|
|
|
|
}
|