feat(auth): OIDC phase 0+1 — discovery, flow detection, client config
Toward MSC3861/MSC2965 next-gen-auth login (P4-6), client-only.
- cs-api.ts: type the stable `m.authentication` well-known key + getOidcIssuer()
(stable preferred over the unstable msc2965 key; {} for non-OIDC servers).
- useParsedLoginFlows.ts: getOidcCompatibilityFlag() (MSC3824 oauth_aware_preferred
/ delegated_oidc_compatibility) as a secondary OIDC hint.
- New pages/auth/oidc/oidcConfig.ts: dynamic-registration client metadata + the
non-hash callback URL (redirect_uris can't contain a fragment).
- paths.ts: OIDC_CALLBACK_PATH.
- 8 unit tests for the pure helpers.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { LoginFlow } from 'matrix-js-sdk/lib/@types/auth';
|
||||
import {
|
||||
getOidcCompatibilityFlag,
|
||||
getSSOFlow,
|
||||
getPasswordFlow,
|
||||
getTokenFlow,
|
||||
} from './useParsedLoginFlows';
|
||||
|
||||
const flows = (...f: Record<string, unknown>[]): LoginFlow[] => f as unknown as LoginFlow[];
|
||||
|
||||
test('flow getters pick the right flow', () => {
|
||||
const f = flows({ type: 'm.login.password' }, { type: 'm.login.sso' }, { type: 'm.login.token' });
|
||||
assert.equal(getPasswordFlow(f)?.type, 'm.login.password');
|
||||
assert.equal(getSSOFlow(f)?.type, 'm.login.sso');
|
||||
assert.equal(getTokenFlow(f)?.type, 'm.login.token');
|
||||
assert.equal(getSSOFlow(flows({ type: 'm.login.cas' }))?.type, 'm.login.cas');
|
||||
});
|
||||
|
||||
test('getOidcCompatibilityFlag detects the stable flag name', () => {
|
||||
assert.equal(
|
||||
getOidcCompatibilityFlag(flows({ type: 'm.login.sso', oauth_aware_preferred: true })),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test('getOidcCompatibilityFlag detects the msc3824 alt name', () => {
|
||||
assert.equal(
|
||||
getOidcCompatibilityFlag(
|
||||
flows({ type: 'm.login.sso', 'org.matrix.msc3824.delegated_oidc_compatibility': true }),
|
||||
),
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test('getOidcCompatibilityFlag is false without sso or without the flag', () => {
|
||||
assert.equal(getOidcCompatibilityFlag(flows({ type: 'm.login.password' })), false);
|
||||
assert.equal(getOidcCompatibilityFlag(flows({ type: 'm.login.sso' })), false);
|
||||
// non-true values do not count
|
||||
assert.equal(
|
||||
getOidcCompatibilityFlag(flows({ type: 'm.login.sso', oauth_aware_preferred: 'yes' })),
|
||||
false,
|
||||
);
|
||||
});
|
||||
@@ -1,5 +1,11 @@
|
||||
import { useMemo } from 'react';
|
||||
import { ILoginFlow, IPasswordFlow, ISSOFlow, LoginFlow } from 'matrix-js-sdk/lib/@types/auth';
|
||||
import {
|
||||
ILoginFlow,
|
||||
IPasswordFlow,
|
||||
ISSOFlow,
|
||||
LoginFlow,
|
||||
OAUTH_AWARE_PREFERRED_FLOW_FIELD,
|
||||
} from 'matrix-js-sdk/lib/@types/auth';
|
||||
|
||||
export const getSSOFlow = (loginFlows: LoginFlow[]): ISSOFlow | undefined =>
|
||||
loginFlows.find((flow) => flow.type === 'm.login.sso' || flow.type === 'm.login.cas') as
|
||||
@@ -13,6 +19,22 @@ export const getTokenFlow = (loginFlows: LoginFlow[]): LoginFlow | undefined =>
|
||||
type: 'm.login.token';
|
||||
};
|
||||
|
||||
/**
|
||||
* MSC3824: a server may advertise `m.login.sso` while signalling (via the
|
||||
* `oauth_aware_preferred` / `org.matrix.msc3824.delegated_oidc_compatibility`
|
||||
* flow field) that it actually prefers native OIDC. This is a *secondary* hint —
|
||||
* the authoritative signal for next-gen auth is an issuer in `.well-known`
|
||||
* discovery (see `getOidcIssuer` in cs-api.ts).
|
||||
*/
|
||||
export const getOidcCompatibilityFlag = (loginFlows: LoginFlow[]): boolean => {
|
||||
const sso = getSSOFlow(loginFlows) as (ISSOFlow & Record<string, unknown>) | undefined;
|
||||
if (!sso) return false;
|
||||
return (
|
||||
sso[OAUTH_AWARE_PREFERRED_FLOW_FIELD.name] === true ||
|
||||
sso[OAUTH_AWARE_PREFERRED_FLOW_FIELD.altName] === true
|
||||
);
|
||||
};
|
||||
|
||||
export type ParsedLoginFlows = {
|
||||
password?: LoginFlow;
|
||||
token?: LoginFlow;
|
||||
|
||||
Reference in New Issue
Block a user