@starci/auth 0.5.0 → 0.6.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 +11 -0
- package/README.md +103 -14
- package/dist/client/oidc/index.d.ts +5 -0
- package/dist/client/oidc/index.js +3 -0
- package/dist/client/oidc/oidc-client.d.ts +85 -0
- package/dist/client/oidc/oidc-client.js +304 -0
- package/dist/client/oidc/pkce.d.ts +15 -0
- package/dist/client/oidc/pkce.js +29 -0
- package/dist/contracts/index.d.ts +12 -0
- package/dist/contracts/index.js +14 -0
- package/dist/contracts/oidc.d.ts +148 -0
- package/dist/contracts/oidc.js +68 -0
- package/dist/oidc/admin.d.ts +42 -0
- package/dist/oidc/admin.js +139 -0
- package/dist/oidc/broker.d.ts +57 -0
- package/dist/oidc/broker.js +77 -0
- package/dist/oidc/client.d.ts +58 -0
- package/dist/oidc/client.js +119 -0
- package/dist/oidc/discovery.d.ts +23 -0
- package/dist/oidc/discovery.js +64 -0
- package/dist/oidc/errors.d.ts +10 -0
- package/dist/oidc/errors.js +10 -0
- package/dist/oidc/grants.d.ts +41 -0
- package/dist/oidc/grants.js +82 -0
- package/dist/oidc/index.d.ts +35 -0
- package/dist/oidc/index.js +18 -0
- package/dist/oidc/issuers/github-actions.d.ts +54 -0
- package/dist/oidc/issuers/github-actions.js +84 -0
- package/dist/oidc/issuers/google.d.ts +30 -0
- package/dist/oidc/issuers/google.js +39 -0
- package/dist/oidc/issuers/index.d.ts +8 -0
- package/dist/oidc/issuers/index.js +4 -0
- package/dist/oidc/issuers/issuer.d.ts +79 -0
- package/dist/oidc/issuers/issuer.js +39 -0
- package/dist/oidc/issuers/keycloak.d.ts +26 -0
- package/dist/oidc/issuers/keycloak.js +41 -0
- package/dist/oidc/jwks.d.ts +26 -0
- package/dist/oidc/jwks.js +80 -0
- package/dist/oidc/oidc.d.ts +32 -0
- package/dist/oidc/oidc.js +21 -0
- package/dist/oidc/oidc.module.d.ts +34 -0
- package/dist/oidc/oidc.module.js +51 -0
- package/dist/oidc/oidc.tokens.d.ts +18 -0
- package/dist/oidc/oidc.tokens.js +18 -0
- package/dist/oidc/options.d.ts +119 -0
- package/dist/oidc/options.js +98 -0
- package/dist/oidc/primitives.d.ts +15 -0
- package/dist/oidc/primitives.js +11 -0
- package/dist/oidc/refresh.d.ts +10 -0
- package/dist/oidc/refresh.js +24 -0
- package/dist/oidc/roles.d.ts +12 -0
- package/dist/oidc/roles.js +51 -0
- package/dist/oidc/tokens.d.ts +26 -0
- package/dist/oidc/tokens.js +79 -0
- package/dist/oidc/transport.d.ts +8 -0
- package/dist/oidc/transport.js +19 -0
- package/dist/oidc/verifier.d.ts +24 -0
- package/dist/oidc/verifier.js +64 -0
- package/dist/server/auth.module.d.ts +1 -1
- package/dist/server/auth.module.js +20 -6
- package/dist/server/auth.tokens.d.ts +2 -0
- package/dist/server/auth.tokens.js +2 -0
- package/dist/server/doors.d.ts +14 -8
- package/dist/server/doors.js +22 -11
- package/dist/server/flows.d.ts +13 -5
- package/dist/server/flows.js +37 -18
- package/dist/server/index.d.ts +7 -7
- package/dist/server/index.js +3 -4
- package/dist/server/options.d.ts +11 -13
- package/dist/server/ports.d.ts +8 -1
- package/dist/server/providers/github.d.ts +59 -0
- package/dist/server/providers/github.js +111 -0
- package/dist/server/providers/google.d.ts +56 -0
- package/dist/server/providers/google.js +87 -0
- package/dist/server/providers/index.d.ts +14 -0
- package/dist/server/providers/index.js +9 -0
- package/dist/server/providers/oauth-state.d.ts +47 -0
- package/dist/server/providers/oauth-state.js +74 -0
- package/dist/server/providers/provider.d.ts +58 -0
- package/dist/server/providers/provider.js +32 -0
- package/dist/server/providers/registry.d.ts +30 -0
- package/dist/server/providers/registry.js +37 -0
- package/dist/server/strategies.d.ts +3 -18
- package/dist/server/strategies.js +2 -41
- package/dist/server/verifier.d.ts +1 -1
- package/dist/testing/index.d.ts +3 -0
- package/dist/testing/index.js +2 -0
- package/dist/testing/oidc.d.ts +175 -0
- package/dist/testing/oidc.js +493 -0
- package/package.json +22 -7
- package/dist/server/google-state.d.ts +0 -33
- package/dist/server/google-state.js +0 -73
package/dist/contracts/index.js
CHANGED
|
@@ -49,6 +49,10 @@ export const AUTH_ERROR_CODES = {
|
|
|
49
49
|
EmailNotConfirmed: "IDENTITY_EMAIL_NOT_CONFIRMED",
|
|
50
50
|
PasswordInvalid: "IDENTITY_PASSWORD_INVALID",
|
|
51
51
|
ResetTokenInvalid: "IDENTITY_RESET_TOKEN_INVALID",
|
|
52
|
+
ProviderStateInvalid: "PASSPORT_PROVIDER_STATE_INVALID",
|
|
53
|
+
ProviderRejected: "PASSPORT_PROVIDER_REJECTED",
|
|
54
|
+
ProviderUnavailable: "PASSPORT_PROVIDER_UNAVAILABLE",
|
|
55
|
+
ProvidersInvalid: "PASSPORT_PROVIDERS_INVALID",
|
|
52
56
|
};
|
|
53
57
|
/** The kind of every code. */
|
|
54
58
|
export const AUTH_ERROR_KINDS = {
|
|
@@ -67,6 +71,16 @@ export const AUTH_ERROR_KINDS = {
|
|
|
67
71
|
[AUTH_ERROR_CODES.EmailNotConfirmed]: "forbidden",
|
|
68
72
|
[AUTH_ERROR_CODES.PasswordInvalid]: "invalid",
|
|
69
73
|
[AUTH_ERROR_CODES.ResetTokenInvalid]: "invalid",
|
|
74
|
+
[AUTH_ERROR_CODES.ProviderStateInvalid]: "unauthenticated",
|
|
75
|
+
[AUTH_ERROR_CODES.ProviderRejected]: "unauthenticated",
|
|
76
|
+
[AUTH_ERROR_CODES.ProviderUnavailable]: "unavailable",
|
|
77
|
+
[AUTH_ERROR_CODES.ProvidersInvalid]: "internal",
|
|
70
78
|
};
|
|
71
79
|
/** The facts both sides hold about a password. */
|
|
72
80
|
export const PASSWORD_BOUNDS = { min: 8, max: 256, bcryptMaxBytes: 72 };
|
|
81
|
+
/** The door that starts a sign-in provider's round trip (`/auth/<id>`); Google's is `AUTH_DOORS.google`. */
|
|
82
|
+
export const providerDoor = (provider) => `/${AUTH_PREFIX}/${provider}`;
|
|
83
|
+
/** The door a sign-in provider sends the browser back to (`/auth/<id>/callback`); Google's is `AUTH_DOORS.googleCallback`. */
|
|
84
|
+
export const providerCallbackDoor = (provider) => `${providerDoor(provider)}/callback`;
|
|
85
|
+
/** The contract of the full edition (OIDC algorithms, codes, claims and principal), merged into this entry. */
|
|
86
|
+
export * from "./oidc.js";
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What both sides of an OIDC sign-in share: the algorithms a token may be signed with, the wire codes, the claim names and the
|
|
3
|
+
* shapes of the answers. The server module and the front-end client read these constants, so the two never drift.
|
|
4
|
+
*/
|
|
5
|
+
/** The signature algorithms a verifier may be pinned to: asymmetric only. `none` and every HMAC algorithm are not in the list and are refused at registration. */
|
|
6
|
+
export declare const OIDC_ALGORITHMS: readonly ["RS256", "RS384", "RS512", "PS256", "PS384", "PS512", "ES256", "ES384", "ES512", "EdDSA"];
|
|
7
|
+
/** One admitted signature algorithm. */
|
|
8
|
+
export type OidcAlgorithm = (typeof OIDC_ALGORITHMS)[number];
|
|
9
|
+
/** The algorithm list a registration pins when it names none: what Keycloak signs with by default. */
|
|
10
|
+
export declare const OIDC_DEFAULT_ALGORITHMS: readonly OidcAlgorithm[];
|
|
11
|
+
/** The `role` every verified principal carries, the one `@starci/auth` gives its principals, so a guard reads one value in both editions. */
|
|
12
|
+
export declare const OIDC_ROLE = "authenticated";
|
|
13
|
+
/**
|
|
14
|
+
* The wire code a verifier refuses a token with: the code `@starci/auth`'s verifier uses, so the guard and the filter that map it
|
|
15
|
+
* to a 401 are the same in both editions. The specific reason travels in the refusal's `detail.reason`, for the log, never for the client.
|
|
16
|
+
*/
|
|
17
|
+
export declare const OIDC_ACCESS_TOKEN_INVALID = "PASSPORT_ACCESS_TOKEN_INVALID";
|
|
18
|
+
/**
|
|
19
|
+
* The codes of the errors the package throws: a provider that cannot be reached is `Unavailable`, a response that cannot be trusted is `Rejected`.
|
|
20
|
+
* The server-side grants add the four a sign-in, a sign-up and a social callback end with: `CredentialsRejected` (the provider refused the
|
|
21
|
+
* username and password, or the refresh token), `EmailRegistered` (the provider already holds the account), `PasswordInvalid` (its policy
|
|
22
|
+
* refuses the password) and `SocialRejected` (a code, nonce or state of a federated sign-in the provider or the check refused).
|
|
23
|
+
*/
|
|
24
|
+
export declare const OIDC_ERROR_CODES: {
|
|
25
|
+
readonly ConfigurationInvalid: "OIDC_CONFIGURATION_INVALID";
|
|
26
|
+
readonly Unavailable: "OIDC_UNAVAILABLE";
|
|
27
|
+
readonly Rejected: "OIDC_REJECTED";
|
|
28
|
+
readonly CredentialsRejected: "OIDC_CREDENTIALS_REJECTED";
|
|
29
|
+
readonly EmailRegistered: "OIDC_EMAIL_REGISTERED";
|
|
30
|
+
readonly PasswordInvalid: "OIDC_PASSWORD_INVALID";
|
|
31
|
+
readonly SocialRejected: "OIDC_SOCIAL_REJECTED";
|
|
32
|
+
};
|
|
33
|
+
/** One code a thrown OIDC error carries. */
|
|
34
|
+
export type OidcErrorCode = (typeof OIDC_ERROR_CODES)[keyof typeof OIDC_ERROR_CODES];
|
|
35
|
+
/** How a thrown code travels: the same families as `@starci/auth`'s error kinds. */
|
|
36
|
+
export type OidcErrorKind = "invalid" | "unavailable" | "unauthenticated" | "credentials-rejected" | "email-registered" | "password-invalid" | "social-rejected";
|
|
37
|
+
/** The kind of every thrown code. */
|
|
38
|
+
export declare const OIDC_ERROR_KINDS: Readonly<Record<OidcErrorCode, OidcErrorKind>>;
|
|
39
|
+
/** Why a token was refused: the reason a refusal's detail names. */
|
|
40
|
+
export type OidcRefusalReason = "malformed" | "algorithm" | "unknown-key" | "signature" | "issuer" | "audience" | "authorized-party" | "expired" | "not-yet-valid" | "token-type" | "claims"
|
|
41
|
+
/** A well-formed token of the right issuer whose claims an issuer preset's policy refuses (another repository, workflow or ref); `detail.claim` names it. */
|
|
42
|
+
| "policy";
|
|
43
|
+
/** The claim names read beyond the registered ones. */
|
|
44
|
+
export declare const OIDC_CLAIMS: {
|
|
45
|
+
readonly sessionId: "sid";
|
|
46
|
+
readonly tokenId: "jti";
|
|
47
|
+
readonly email: "email";
|
|
48
|
+
readonly emailVerified: "email_verified";
|
|
49
|
+
readonly name: "name";
|
|
50
|
+
readonly username: "preferred_username";
|
|
51
|
+
readonly authorizedParty: "azp";
|
|
52
|
+
readonly realmAccess: "realm_access";
|
|
53
|
+
readonly resourceAccess: "resource_access";
|
|
54
|
+
readonly tokenType: "typ";
|
|
55
|
+
readonly nonce: "nonce";
|
|
56
|
+
};
|
|
57
|
+
/** The most roles a token may carry and the longest a role may be: a token past either is refused, so a claim cannot grow without bound. */
|
|
58
|
+
export declare const OIDC_ROLE_BOUNDS: {
|
|
59
|
+
readonly maxRoles: 128;
|
|
60
|
+
readonly maxLength: 256;
|
|
61
|
+
};
|
|
62
|
+
/** The longest `sub` a principal may have. */
|
|
63
|
+
export declare const OIDC_SUBJECT_MAX_LENGTH = 255;
|
|
64
|
+
/** The principal a verified access token stands for. Assignable to `@starci/auth`'s `AuthPrincipal`, with the roles the issuer granted. */
|
|
65
|
+
export interface OidcPrincipal {
|
|
66
|
+
/** The account: the `sub` claim. */
|
|
67
|
+
readonly id: string;
|
|
68
|
+
/** Always `authenticated`: a guard that checks the role of the lite principal checks this one the same way. */
|
|
69
|
+
readonly role: typeof OIDC_ROLE;
|
|
70
|
+
/** The provider session: the `sid` claim, else the token id (`jti`), else the subject. */
|
|
71
|
+
readonly sessionId: string;
|
|
72
|
+
/** The verified `email` claim, when the token has one. */
|
|
73
|
+
readonly email?: string;
|
|
74
|
+
/** The display name, when the token has one. */
|
|
75
|
+
readonly name?: string;
|
|
76
|
+
/** The `preferred_username`, when the token has one. */
|
|
77
|
+
readonly username?: string;
|
|
78
|
+
/** The roles the issuer granted, from the realm and from the clients the registration maps, sorted and without repeats. */
|
|
79
|
+
readonly roles: readonly string[];
|
|
80
|
+
/** The issuer that signed the token. */
|
|
81
|
+
readonly issuer: string;
|
|
82
|
+
/** When the token stops being accepted. */
|
|
83
|
+
readonly expiresAt: Date;
|
|
84
|
+
}
|
|
85
|
+
/** The refusal of a verifier. */
|
|
86
|
+
export interface OidcRefusal {
|
|
87
|
+
readonly kind: "refused";
|
|
88
|
+
readonly code: typeof OIDC_ACCESS_TOKEN_INVALID;
|
|
89
|
+
readonly detail?: Readonly<Record<string, unknown>>;
|
|
90
|
+
}
|
|
91
|
+
/** The result of a check that can refuse: the same two shapes as `@starci/auth`'s `Outcome`. */
|
|
92
|
+
export type OidcOutcome<T> = {
|
|
93
|
+
readonly kind: "ok";
|
|
94
|
+
readonly value: T;
|
|
95
|
+
} | OidcRefusal;
|
|
96
|
+
/**
|
|
97
|
+
* What a guard asks of an identity source: the principal of a bearer token, or a refusal. The same interface as
|
|
98
|
+
* `PrincipalVerifier` of `@starci/auth/server` (the package's own specs hold that this verifier is assignable to that one, and its principal to `AuthPrincipal`), so
|
|
99
|
+
* guards, `@Public` and `@Credential` do not change between editions.
|
|
100
|
+
*/
|
|
101
|
+
export interface PrincipalVerifier {
|
|
102
|
+
/** Verifies one token at one instant. A provider that cannot be reached throws an `OidcError` of kind `unavailable`; an untrusted token is a refusal. */
|
|
103
|
+
verify(token: string, now?: Date): Promise<OidcOutcome<OidcPrincipal>>;
|
|
104
|
+
}
|
|
105
|
+
/** What a completed authorization-code flow established, after the ID token passed signature, issuer, audience, expiry and nonce. */
|
|
106
|
+
export interface OidcIdentity {
|
|
107
|
+
/** The issuer that signed the ID token. */
|
|
108
|
+
readonly issuer: string;
|
|
109
|
+
/** The verified account subject. */
|
|
110
|
+
readonly subject: string;
|
|
111
|
+
/** The roles the ID token carries. */
|
|
112
|
+
readonly roles: readonly string[];
|
|
113
|
+
/** The verified email, when the token has one. */
|
|
114
|
+
readonly email?: string;
|
|
115
|
+
/** The provider session, when the token names one. */
|
|
116
|
+
readonly sessionId?: string;
|
|
117
|
+
/** The verified token expiry, which bounds a local session. */
|
|
118
|
+
readonly expiresAt: Date;
|
|
119
|
+
}
|
|
120
|
+
/** The person a front-end session belongs to. */
|
|
121
|
+
export interface OidcSessionUser {
|
|
122
|
+
/** The subject. */
|
|
123
|
+
readonly id: string;
|
|
124
|
+
/** The email, or empty text when the token has none. */
|
|
125
|
+
readonly email: string;
|
|
126
|
+
}
|
|
127
|
+
/** The kinds of failure a front-end call ends with. */
|
|
128
|
+
export type OidcFailureKind = "refused" | "not-found" | "invalid" | "unavailable";
|
|
129
|
+
/** The outcome of a front-end call: a value, or a failure carrying its kind and a code. The same shape as `AuthOutcome` of `@starci/auth/contracts`. */
|
|
130
|
+
export type OidcClientOutcome<T> = {
|
|
131
|
+
readonly success: true;
|
|
132
|
+
readonly message: "ok";
|
|
133
|
+
readonly data: T;
|
|
134
|
+
} | {
|
|
135
|
+
readonly success: false;
|
|
136
|
+
readonly message: OidcFailureKind;
|
|
137
|
+
readonly error: string;
|
|
138
|
+
};
|
|
139
|
+
/** The codes a front-end failure carries. */
|
|
140
|
+
export declare const OIDC_CLIENT_CODES: {
|
|
141
|
+
readonly State: "OIDC_STATE_INVALID";
|
|
142
|
+
readonly Nonce: "OIDC_NONCE_INVALID";
|
|
143
|
+
readonly Issuer: "OIDC_ISSUER_MISMATCH";
|
|
144
|
+
readonly Provider: "OIDC_PROVIDER_REFUSED";
|
|
145
|
+
readonly Token: "OIDC_TOKEN_RESPONSE_INVALID";
|
|
146
|
+
readonly Transport: "OIDC_TRANSPORT";
|
|
147
|
+
readonly NoLogout: "OIDC_NO_LOGOUT_ENDPOINT";
|
|
148
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What both sides of an OIDC sign-in share: the algorithms a token may be signed with, the wire codes, the claim names and the
|
|
3
|
+
* shapes of the answers. The server module and the front-end client read these constants, so the two never drift.
|
|
4
|
+
*/
|
|
5
|
+
/** The signature algorithms a verifier may be pinned to: asymmetric only. `none` and every HMAC algorithm are not in the list and are refused at registration. */
|
|
6
|
+
export const OIDC_ALGORITHMS = ["RS256", "RS384", "RS512", "PS256", "PS384", "PS512", "ES256", "ES384", "ES512", "EdDSA"];
|
|
7
|
+
/** The algorithm list a registration pins when it names none: what Keycloak signs with by default. */
|
|
8
|
+
export const OIDC_DEFAULT_ALGORITHMS = ["RS256"];
|
|
9
|
+
/** The `role` every verified principal carries, the one `@starci/auth` gives its principals, so a guard reads one value in both editions. */
|
|
10
|
+
export const OIDC_ROLE = "authenticated";
|
|
11
|
+
/**
|
|
12
|
+
* The wire code a verifier refuses a token with: the code `@starci/auth`'s verifier uses, so the guard and the filter that map it
|
|
13
|
+
* to a 401 are the same in both editions. The specific reason travels in the refusal's `detail.reason`, for the log, never for the client.
|
|
14
|
+
*/
|
|
15
|
+
export const OIDC_ACCESS_TOKEN_INVALID = "PASSPORT_ACCESS_TOKEN_INVALID";
|
|
16
|
+
/**
|
|
17
|
+
* The codes of the errors the package throws: a provider that cannot be reached is `Unavailable`, a response that cannot be trusted is `Rejected`.
|
|
18
|
+
* The server-side grants add the four a sign-in, a sign-up and a social callback end with: `CredentialsRejected` (the provider refused the
|
|
19
|
+
* username and password, or the refresh token), `EmailRegistered` (the provider already holds the account), `PasswordInvalid` (its policy
|
|
20
|
+
* refuses the password) and `SocialRejected` (a code, nonce or state of a federated sign-in the provider or the check refused).
|
|
21
|
+
*/
|
|
22
|
+
export const OIDC_ERROR_CODES = {
|
|
23
|
+
ConfigurationInvalid: "OIDC_CONFIGURATION_INVALID",
|
|
24
|
+
Unavailable: "OIDC_UNAVAILABLE",
|
|
25
|
+
Rejected: "OIDC_REJECTED",
|
|
26
|
+
CredentialsRejected: "OIDC_CREDENTIALS_REJECTED",
|
|
27
|
+
EmailRegistered: "OIDC_EMAIL_REGISTERED",
|
|
28
|
+
PasswordInvalid: "OIDC_PASSWORD_INVALID",
|
|
29
|
+
SocialRejected: "OIDC_SOCIAL_REJECTED",
|
|
30
|
+
};
|
|
31
|
+
/** The kind of every thrown code. */
|
|
32
|
+
export const OIDC_ERROR_KINDS = {
|
|
33
|
+
[OIDC_ERROR_CODES.ConfigurationInvalid]: "invalid",
|
|
34
|
+
[OIDC_ERROR_CODES.Unavailable]: "unavailable",
|
|
35
|
+
[OIDC_ERROR_CODES.Rejected]: "unauthenticated",
|
|
36
|
+
[OIDC_ERROR_CODES.CredentialsRejected]: "credentials-rejected",
|
|
37
|
+
[OIDC_ERROR_CODES.EmailRegistered]: "email-registered",
|
|
38
|
+
[OIDC_ERROR_CODES.PasswordInvalid]: "password-invalid",
|
|
39
|
+
[OIDC_ERROR_CODES.SocialRejected]: "social-rejected",
|
|
40
|
+
};
|
|
41
|
+
/** The claim names read beyond the registered ones. */
|
|
42
|
+
export const OIDC_CLAIMS = {
|
|
43
|
+
sessionId: "sid",
|
|
44
|
+
tokenId: "jti",
|
|
45
|
+
email: "email",
|
|
46
|
+
emailVerified: "email_verified",
|
|
47
|
+
name: "name",
|
|
48
|
+
username: "preferred_username",
|
|
49
|
+
authorizedParty: "azp",
|
|
50
|
+
realmAccess: "realm_access",
|
|
51
|
+
resourceAccess: "resource_access",
|
|
52
|
+
tokenType: "typ",
|
|
53
|
+
nonce: "nonce",
|
|
54
|
+
};
|
|
55
|
+
/** The most roles a token may carry and the longest a role may be: a token past either is refused, so a claim cannot grow without bound. */
|
|
56
|
+
export const OIDC_ROLE_BOUNDS = { maxRoles: 128, maxLength: 256 };
|
|
57
|
+
/** The longest `sub` a principal may have. */
|
|
58
|
+
export const OIDC_SUBJECT_MAX_LENGTH = 255;
|
|
59
|
+
/** The codes a front-end failure carries. */
|
|
60
|
+
export const OIDC_CLIENT_CODES = {
|
|
61
|
+
State: "OIDC_STATE_INVALID",
|
|
62
|
+
Nonce: "OIDC_NONCE_INVALID",
|
|
63
|
+
Issuer: "OIDC_ISSUER_MISMATCH",
|
|
64
|
+
Provider: "OIDC_PROVIDER_REFUSED",
|
|
65
|
+
Token: "OIDC_TOKEN_RESPONSE_INVALID",
|
|
66
|
+
Transport: "OIDC_TRANSPORT",
|
|
67
|
+
NoLogout: "OIDC_NO_LOGOUT_ENDPOINT",
|
|
68
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { OidcDiscovery } from "./discovery.js";
|
|
2
|
+
import type { ResolvedOidcOptions } from "./options.js";
|
|
3
|
+
/** The account a sign-up creates. */
|
|
4
|
+
export interface OidcNewUser {
|
|
5
|
+
readonly email: string;
|
|
6
|
+
readonly username: string;
|
|
7
|
+
/** The first password; it is set permanent (not temporary) and is sent once. */
|
|
8
|
+
readonly password: string;
|
|
9
|
+
}
|
|
10
|
+
/** The account a sign-up created. */
|
|
11
|
+
export interface OidcCreatedUser {
|
|
12
|
+
/** The provider's id of the account: the `sub` of its tokens. */
|
|
13
|
+
readonly subject: string;
|
|
14
|
+
}
|
|
15
|
+
/** The account an email belongs to. */
|
|
16
|
+
export interface OidcFoundUser {
|
|
17
|
+
readonly subject: string;
|
|
18
|
+
readonly username: string;
|
|
19
|
+
}
|
|
20
|
+
/** The realm admin base, `<origin>/admin/realms/<realm>`, derived from a Keycloak issuer `<origin>[/<prefix>]/realms/<realm>`. */
|
|
21
|
+
export declare const adminBaseOf: (issuer: string) => string;
|
|
22
|
+
/**
|
|
23
|
+
* The provider's admin API as the registered client's service account (the client credentials grant, the token cached until it is about
|
|
24
|
+
* to expire): create an account, find one by email, set a password, end every session. The realm admin base is derived from the issuer.
|
|
25
|
+
* The service account needs the realm-management roles for users; a request it cannot make is `Rejected`. Nothing here logs a
|
|
26
|
+
* password or a token.
|
|
27
|
+
*/
|
|
28
|
+
export declare class OidcAdminClient {
|
|
29
|
+
#private;
|
|
30
|
+
constructor(options: ResolvedOidcOptions, discovery: OidcDiscovery);
|
|
31
|
+
/**
|
|
32
|
+
* Creates an enabled account with a permanent password. A taken email or username throws `EmailRegistered`; a password the realm's policy
|
|
33
|
+
* refuses throws `PasswordInvalid`.
|
|
34
|
+
*/
|
|
35
|
+
createUser(input: OidcNewUser): Promise<OidcCreatedUser>;
|
|
36
|
+
/** The account whose email is exactly this one (case-insensitive), or null. */
|
|
37
|
+
findUserByEmail(email: string): Promise<OidcFoundUser | null>;
|
|
38
|
+
/** Sets a permanent password. A password the policy refuses throws `PasswordInvalid`. */
|
|
39
|
+
resetPassword(subject: string, password: string): Promise<void>;
|
|
40
|
+
/** Ends every session of the account at the provider, which also ends their refresh tokens. */
|
|
41
|
+
logoutAll(subject: string): Promise<void>;
|
|
42
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { OIDC_ERROR_CODES } from "../contracts/oidc.js";
|
|
2
|
+
import { OidcError } from "./errors.js";
|
|
3
|
+
import { isRecord, revealed } from "./primitives.js";
|
|
4
|
+
import { send } from "./transport.js";
|
|
5
|
+
/** A service-account token is renewed this long before it expires, so a request never carries one that dies in flight. */
|
|
6
|
+
const TOKEN_MARGIN_MS = 10_000;
|
|
7
|
+
const SECOND_MS = 1000;
|
|
8
|
+
const rejected = () => new OidcError(OIDC_ERROR_CODES.Rejected);
|
|
9
|
+
/** True when an answer's error text speaks of the password (the provider's policy refusing it), not of another field. */
|
|
10
|
+
const speaksOfPassword = (body) => {
|
|
11
|
+
if (!isRecord(body))
|
|
12
|
+
return false;
|
|
13
|
+
return [body.error, body.errorMessage, body.error_description].some((field) => typeof field === "string" && /password/iu.test(field));
|
|
14
|
+
};
|
|
15
|
+
/** The last path segment of a `Location` address: the id the provider gave a created account. */
|
|
16
|
+
const idOfLocation = (location) => {
|
|
17
|
+
if (location === undefined || !URL.canParse(location))
|
|
18
|
+
return undefined;
|
|
19
|
+
const segment = new URL(location).pathname
|
|
20
|
+
.split("/")
|
|
21
|
+
.filter((part) => part !== "")
|
|
22
|
+
.pop();
|
|
23
|
+
return segment === undefined ? undefined : decodeURIComponent(segment);
|
|
24
|
+
};
|
|
25
|
+
/** The realm admin base, `<origin>/admin/realms/<realm>`, derived from a Keycloak issuer `<origin>[/<prefix>]/realms/<realm>`. */
|
|
26
|
+
export const adminBaseOf = (issuer) => {
|
|
27
|
+
const url = new URL(issuer);
|
|
28
|
+
const path = url.pathname.replace(/\/$/u, "");
|
|
29
|
+
const at = path.lastIndexOf("/realms/");
|
|
30
|
+
const realm = at < 0 ? "" : path.slice(at + "/realms/".length);
|
|
31
|
+
if (realm === "" || realm.includes("/"))
|
|
32
|
+
throw new OidcError(OIDC_ERROR_CODES.ConfigurationInvalid, { params: { key: "issuer" } });
|
|
33
|
+
return `${url.origin}${path.slice(0, at)}/admin/realms/${realm}`;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* The provider's admin API as the registered client's service account (the client credentials grant, the token cached until it is about
|
|
37
|
+
* to expire): create an account, find one by email, set a password, end every session. The realm admin base is derived from the issuer.
|
|
38
|
+
* The service account needs the realm-management roles for users; a request it cannot make is `Rejected`. Nothing here logs a
|
|
39
|
+
* password or a token.
|
|
40
|
+
*/
|
|
41
|
+
export class OidcAdminClient {
|
|
42
|
+
#options;
|
|
43
|
+
#discovery;
|
|
44
|
+
#held;
|
|
45
|
+
#pending;
|
|
46
|
+
constructor(options, discovery) {
|
|
47
|
+
this.#options = options;
|
|
48
|
+
this.#discovery = discovery;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Creates an enabled account with a permanent password. A taken email or username throws `EmailRegistered`; a password the realm's policy
|
|
52
|
+
* refuses throws `PasswordInvalid`.
|
|
53
|
+
*/
|
|
54
|
+
async createUser(input) {
|
|
55
|
+
const response = await this.#call("POST", "/users", {
|
|
56
|
+
username: input.username,
|
|
57
|
+
email: input.email,
|
|
58
|
+
enabled: true,
|
|
59
|
+
emailVerified: false,
|
|
60
|
+
credentials: [{ type: "password", value: input.password, temporary: false }],
|
|
61
|
+
});
|
|
62
|
+
if (response.status === 409)
|
|
63
|
+
throw new OidcError(OIDC_ERROR_CODES.EmailRegistered);
|
|
64
|
+
if (response.status === 400 && speaksOfPassword(response.body))
|
|
65
|
+
throw new OidcError(OIDC_ERROR_CODES.PasswordInvalid);
|
|
66
|
+
if (response.status !== 201)
|
|
67
|
+
throw rejected();
|
|
68
|
+
const subject = idOfLocation(response.location) ?? (await this.#byUsername(input.username));
|
|
69
|
+
if (subject === undefined)
|
|
70
|
+
throw rejected();
|
|
71
|
+
return { subject };
|
|
72
|
+
}
|
|
73
|
+
/** The account whose email is exactly this one (case-insensitive), or null. */
|
|
74
|
+
async findUserByEmail(email) {
|
|
75
|
+
const users = await this.#search({ email, exact: "true" });
|
|
76
|
+
const wanted = email.toLowerCase();
|
|
77
|
+
const found = users.find((user) => user.email?.toLowerCase() === wanted);
|
|
78
|
+
return found === undefined ? null : { subject: found.id, username: found.username };
|
|
79
|
+
}
|
|
80
|
+
/** Sets a permanent password. A password the policy refuses throws `PasswordInvalid`. */
|
|
81
|
+
async resetPassword(subject, password) {
|
|
82
|
+
const response = await this.#call("PUT", `/users/${encodeURIComponent(subject)}/reset-password`, { type: "password", value: password, temporary: false });
|
|
83
|
+
if (response.status === 400)
|
|
84
|
+
throw new OidcError(OIDC_ERROR_CODES.PasswordInvalid);
|
|
85
|
+
if (response.status !== 204 && response.status !== 200)
|
|
86
|
+
throw rejected();
|
|
87
|
+
}
|
|
88
|
+
/** Ends every session of the account at the provider, which also ends their refresh tokens. */
|
|
89
|
+
async logoutAll(subject) {
|
|
90
|
+
const response = await this.#call("POST", `/users/${encodeURIComponent(subject)}/logout`);
|
|
91
|
+
if (response.status !== 204 && response.status !== 200)
|
|
92
|
+
throw rejected();
|
|
93
|
+
}
|
|
94
|
+
async #byUsername(username) {
|
|
95
|
+
const users = await this.#search({ username, exact: "true" });
|
|
96
|
+
return users.find((user) => user.username.toLowerCase() === username.toLowerCase())?.id;
|
|
97
|
+
}
|
|
98
|
+
async #search(query) {
|
|
99
|
+
const response = await this.#call("GET", `/users?${new URLSearchParams(query).toString()}`);
|
|
100
|
+
if (response.status !== 200 || !Array.isArray(response.body))
|
|
101
|
+
throw rejected();
|
|
102
|
+
const list = response.body;
|
|
103
|
+
return list.flatMap((entry) => isRecord(entry) && typeof entry.id === "string" && entry.id !== "" && typeof entry.username === "string"
|
|
104
|
+
? [{ id: entry.id, username: entry.username, ...(typeof entry.email === "string" ? { email: entry.email } : {}) }]
|
|
105
|
+
: []);
|
|
106
|
+
}
|
|
107
|
+
/** One admin request with the service-account token; a 401 means the token died early, so it is renewed and the request is repeated once. */
|
|
108
|
+
async #call(method, path, body) {
|
|
109
|
+
const url = `${adminBaseOf(this.#options.issuer)}${path}`;
|
|
110
|
+
const attempt = async (renew) => send(this.#options, { method, url, headers: { authorization: `Bearer ${await this.#token(renew)}` }, ...(body === undefined ? {} : { body }) });
|
|
111
|
+
const first = await attempt(false);
|
|
112
|
+
return first.status === 401 ? attempt(true) : first;
|
|
113
|
+
}
|
|
114
|
+
#token(renew) {
|
|
115
|
+
const now = this.#options.clock.now().getTime();
|
|
116
|
+
if (!renew && this.#held !== undefined && this.#held.expiresAt - TOKEN_MARGIN_MS > now)
|
|
117
|
+
return Promise.resolve(this.#held.token);
|
|
118
|
+
this.#pending ??= this.#fetch().finally(() => {
|
|
119
|
+
this.#pending = undefined;
|
|
120
|
+
});
|
|
121
|
+
return this.#pending;
|
|
122
|
+
}
|
|
123
|
+
async #fetch() {
|
|
124
|
+
const client = this.#options.client;
|
|
125
|
+
if (client?.clientSecret === undefined)
|
|
126
|
+
throw new OidcError(OIDC_ERROR_CODES.ConfigurationInvalid, { params: { key: "client.clientSecret" } });
|
|
127
|
+
const { tokenEndpoint } = await this.#discovery.get();
|
|
128
|
+
const response = await send(this.#options, {
|
|
129
|
+
method: "POST",
|
|
130
|
+
url: tokenEndpoint,
|
|
131
|
+
form: { grant_type: "client_credentials", client_id: client.clientId, client_secret: revealed(client.clientSecret) },
|
|
132
|
+
});
|
|
133
|
+
const answer = response.body;
|
|
134
|
+
if (response.status !== 200 || !isRecord(answer) || typeof answer.access_token !== "string" || typeof answer.expires_in !== "number" || !(answer.expires_in > 0))
|
|
135
|
+
throw rejected();
|
|
136
|
+
this.#held = { token: answer.access_token, expiresAt: this.#options.clock.now().getTime() + answer.expires_in * SECOND_MS };
|
|
137
|
+
return answer.access_token;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { OidcAdminClient, OidcCreatedUser, OidcFoundUser, OidcNewUser } from "./admin.js";
|
|
2
|
+
import type { OidcClient } from "./client.js";
|
|
3
|
+
import type { OidcTokenClient } from "./grants.js";
|
|
4
|
+
/** What a sign-in, a refresh or a social callback answers: the account, its roles and email, and the provider session's refresh token. No access token. */
|
|
5
|
+
export interface OidcBrokerSession {
|
|
6
|
+
readonly subject: string;
|
|
7
|
+
readonly roles: readonly string[];
|
|
8
|
+
readonly email: string | null;
|
|
9
|
+
readonly refreshToken: string;
|
|
10
|
+
readonly refreshExpiresAt: Date;
|
|
11
|
+
}
|
|
12
|
+
/** The proof values of a federated sign-in the app owns, and the identity provider the realm brokers. */
|
|
13
|
+
export interface OidcSocialRequest {
|
|
14
|
+
/** The alias of the identity provider in the realm (`google`, `github`), sent as `kc_idp_hint`. */
|
|
15
|
+
readonly provider: string;
|
|
16
|
+
readonly state: string;
|
|
17
|
+
readonly nonce: string;
|
|
18
|
+
/** The S256 challenge of the PKCE verifier the app keeps (`pkceChallenge`). */
|
|
19
|
+
readonly codeChallenge: string;
|
|
20
|
+
}
|
|
21
|
+
/** The callback of a federated sign-in, with the proof values the app kept. */
|
|
22
|
+
export interface OidcSocialCallback {
|
|
23
|
+
readonly code: string;
|
|
24
|
+
readonly codeVerifier: string;
|
|
25
|
+
readonly nonce: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The one facade an app binds its identity port to when Keycloak sits behind the back end: the password grant, the refresh and the
|
|
29
|
+
* revocation of the provider session; the federated sign-in (the authorization address with `kc_idp_hint` and the code exchange); and the
|
|
30
|
+
* account administration (create, find by email, reset the password, end every session). The tokens stay on the server: a session answer
|
|
31
|
+
* carries the verified account, the refresh token and its expiry, never an access token.
|
|
32
|
+
*
|
|
33
|
+
* Failures are `OidcError`s: `unavailable` (the provider is down or throttling), `credentials-rejected` (a bad password or a dead refresh
|
|
34
|
+
* token), `email-registered`, `password-invalid` and `social-rejected` (a code, nonce or state the provider or the check refused).
|
|
35
|
+
*/
|
|
36
|
+
export declare class OidcBroker {
|
|
37
|
+
#private;
|
|
38
|
+
constructor(tokens: OidcTokenClient, client: OidcClient | null, admin: OidcAdminClient);
|
|
39
|
+
/** Signs in with a username or email and a password. */
|
|
40
|
+
passwordGrant(identifier: string, password: string): Promise<OidcBrokerSession>;
|
|
41
|
+
/** Renews a session from its refresh token; the answer holds the rotated refresh token. */
|
|
42
|
+
refresh(refreshToken: string): Promise<OidcBrokerSession>;
|
|
43
|
+
/** Ends the provider session of a refresh token. */
|
|
44
|
+
revoke(refreshToken: string): Promise<void>;
|
|
45
|
+
/** The address the browser goes to for a federated sign-in at the identity provider `provider`. */
|
|
46
|
+
socialAuthorizationUrl(input: OidcSocialRequest): Promise<string>;
|
|
47
|
+
/** Exchanges the code of a federated sign-in once. A code, nonce or answer the provider or the check refuses throws `social-rejected`; an outage stays `unavailable`. */
|
|
48
|
+
exchangeSocialCode(input: OidcSocialCallback): Promise<OidcBrokerSession>;
|
|
49
|
+
/** Creates an account with a permanent password. */
|
|
50
|
+
createUser(input: OidcNewUser): Promise<OidcCreatedUser>;
|
|
51
|
+
/** The account an email belongs to, or null. */
|
|
52
|
+
findUserByEmail(email: string): Promise<OidcFoundUser | null>;
|
|
53
|
+
/** Sets a permanent password. */
|
|
54
|
+
resetPassword(subject: string, password: string): Promise<void>;
|
|
55
|
+
/** Ends every provider session of the account. */
|
|
56
|
+
logoutAll(subject: string): Promise<void>;
|
|
57
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { OIDC_ERROR_CODES } from "../contracts/oidc.js";
|
|
2
|
+
import { OidcError } from "./errors.js";
|
|
3
|
+
const session = (grant) => ({
|
|
4
|
+
subject: grant.subject,
|
|
5
|
+
roles: grant.roles,
|
|
6
|
+
email: grant.email,
|
|
7
|
+
refreshToken: grant.refreshToken,
|
|
8
|
+
refreshExpiresAt: grant.refreshExpiresAt,
|
|
9
|
+
});
|
|
10
|
+
/**
|
|
11
|
+
* The one facade an app binds its identity port to when Keycloak sits behind the back end: the password grant, the refresh and the
|
|
12
|
+
* revocation of the provider session; the federated sign-in (the authorization address with `kc_idp_hint` and the code exchange); and the
|
|
13
|
+
* account administration (create, find by email, reset the password, end every session). The tokens stay on the server: a session answer
|
|
14
|
+
* carries the verified account, the refresh token and its expiry, never an access token.
|
|
15
|
+
*
|
|
16
|
+
* Failures are `OidcError`s: `unavailable` (the provider is down or throttling), `credentials-rejected` (a bad password or a dead refresh
|
|
17
|
+
* token), `email-registered`, `password-invalid` and `social-rejected` (a code, nonce or state the provider or the check refused).
|
|
18
|
+
*/
|
|
19
|
+
export class OidcBroker {
|
|
20
|
+
#tokens;
|
|
21
|
+
#client;
|
|
22
|
+
#admin;
|
|
23
|
+
constructor(tokens, client, admin) {
|
|
24
|
+
this.#tokens = tokens;
|
|
25
|
+
this.#client = client;
|
|
26
|
+
this.#admin = admin;
|
|
27
|
+
}
|
|
28
|
+
/** Signs in with a username or email and a password. */
|
|
29
|
+
async passwordGrant(identifier, password) {
|
|
30
|
+
return session(await this.#tokens.passwordGrant(identifier, password));
|
|
31
|
+
}
|
|
32
|
+
/** Renews a session from its refresh token; the answer holds the rotated refresh token. */
|
|
33
|
+
async refresh(refreshToken) {
|
|
34
|
+
return session(await this.#tokens.refresh(refreshToken));
|
|
35
|
+
}
|
|
36
|
+
/** Ends the provider session of a refresh token. */
|
|
37
|
+
async revoke(refreshToken) {
|
|
38
|
+
await this.#tokens.revoke(refreshToken);
|
|
39
|
+
}
|
|
40
|
+
/** The address the browser goes to for a federated sign-in at the identity provider `provider`. */
|
|
41
|
+
async socialAuthorizationUrl(input) {
|
|
42
|
+
return this.#registered().authorization({ state: input.state, nonce: input.nonce, challenge: input.codeChallenge, idpHint: input.provider });
|
|
43
|
+
}
|
|
44
|
+
/** Exchanges the code of a federated sign-in once. A code, nonce or answer the provider or the check refuses throws `social-rejected`; an outage stays `unavailable`. */
|
|
45
|
+
async exchangeSocialCode(input) {
|
|
46
|
+
const rejected = new OidcError(OIDC_ERROR_CODES.SocialRejected);
|
|
47
|
+
const identity = await this.#registered()
|
|
48
|
+
.exchange({ code: input.code, verifier: input.codeVerifier, nonce: input.nonce })
|
|
49
|
+
.catch((cause) => {
|
|
50
|
+
throw cause instanceof OidcError && cause.code === OIDC_ERROR_CODES.Rejected ? rejected : cause;
|
|
51
|
+
});
|
|
52
|
+
if (identity.refreshToken === undefined || identity.refreshExpiresAt === undefined)
|
|
53
|
+
throw rejected;
|
|
54
|
+
return { subject: identity.subject, roles: identity.roles, email: identity.email ?? null, refreshToken: identity.refreshToken, refreshExpiresAt: identity.refreshExpiresAt };
|
|
55
|
+
}
|
|
56
|
+
/** Creates an account with a permanent password. */
|
|
57
|
+
createUser(input) {
|
|
58
|
+
return this.#admin.createUser(input);
|
|
59
|
+
}
|
|
60
|
+
/** The account an email belongs to, or null. */
|
|
61
|
+
findUserByEmail(email) {
|
|
62
|
+
return this.#admin.findUserByEmail(email);
|
|
63
|
+
}
|
|
64
|
+
/** Sets a permanent password. */
|
|
65
|
+
resetPassword(subject, password) {
|
|
66
|
+
return this.#admin.resetPassword(subject, password);
|
|
67
|
+
}
|
|
68
|
+
/** Ends every provider session of the account. */
|
|
69
|
+
logoutAll(subject) {
|
|
70
|
+
return this.#admin.logoutAll(subject);
|
|
71
|
+
}
|
|
72
|
+
#registered() {
|
|
73
|
+
if (this.#client === null)
|
|
74
|
+
throw new OidcError(OIDC_ERROR_CODES.ConfigurationInvalid, { params: { key: "client" } });
|
|
75
|
+
return this.#client;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { OidcIdentity } from "../contracts/oidc.js";
|
|
2
|
+
import type { OidcDiscovery } from "./discovery.js";
|
|
3
|
+
import type { ResolvedOidcOptions } from "./options.js";
|
|
4
|
+
import type { OidcTokenVerifier } from "./tokens.js";
|
|
5
|
+
/** The proof values the app owns and sends to the authorization endpoint. */
|
|
6
|
+
export interface OidcAuthorization {
|
|
7
|
+
/** One-use transaction state. */
|
|
8
|
+
readonly state: string;
|
|
9
|
+
/** Binds the ID token to the browser transaction. */
|
|
10
|
+
readonly nonce: string;
|
|
11
|
+
/** The base64url SHA-256 digest of the retained PKCE verifier (`pkceChallenge`). */
|
|
12
|
+
readonly challenge: string;
|
|
13
|
+
/** `none`, `login`, `consent` or `select_account`, when the flow must not (or must) show a login page. */
|
|
14
|
+
readonly prompt?: "none" | "login" | "consent" | "select_account" | undefined;
|
|
15
|
+
/** The alias of an identity provider the realm brokers (`google`), sent as `kc_idp_hint` so the provider skips its own login page for it. */
|
|
16
|
+
readonly idpHint?: string | undefined;
|
|
17
|
+
}
|
|
18
|
+
/** A claimed callback, exchanged exactly once. */
|
|
19
|
+
export interface OidcExchange {
|
|
20
|
+
/** The transient authorization code; never returned to the browser. */
|
|
21
|
+
readonly code: string;
|
|
22
|
+
/** The retained PKCE verifier. */
|
|
23
|
+
readonly verifier: string;
|
|
24
|
+
/** The nonce retained with the transaction. */
|
|
25
|
+
readonly nonce: string;
|
|
26
|
+
/** The `iss` of the authorization response, when the provider sent one; a supplied issuer must equal the configured one. */
|
|
27
|
+
readonly issuer?: string | undefined;
|
|
28
|
+
}
|
|
29
|
+
/** What a completed code exchange established: the identity, and the provider's refresh token when it issued one. The refresh token stays on the server. */
|
|
30
|
+
export interface OidcExchangeResult extends OidcIdentity {
|
|
31
|
+
/** The refresh token of the provider session; absent when the provider issued none or none that can be dated. */
|
|
32
|
+
readonly refreshToken?: string;
|
|
33
|
+
/** When the refresh token stops being accepted; present with `refreshToken`. */
|
|
34
|
+
readonly refreshExpiresAt?: Date;
|
|
35
|
+
}
|
|
36
|
+
/** The address a browser is sent to for RP-initiated logout. */
|
|
37
|
+
export interface OidcLogout {
|
|
38
|
+
/** Sent as `id_token_hint` when the app retained the ID token. */
|
|
39
|
+
readonly idTokenHint?: string | undefined;
|
|
40
|
+
/** Sent as `state`. */
|
|
41
|
+
readonly state?: string | undefined;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The authorization code flow of one registered client: it builds the authorization address (S256 PKCE, nonce, state), exchanges a
|
|
45
|
+
* code once at the token endpoint and verifies the ID token (signature, issuer, audience = the client, expiry, nonce) before it answers
|
|
46
|
+
* an identity, and builds the RP-initiated logout address. It never logs a provider token; the refresh token a code exchange answers is for the
|
|
47
|
+
* server's own store, never for a browser.
|
|
48
|
+
*/
|
|
49
|
+
export declare class OidcClient {
|
|
50
|
+
#private;
|
|
51
|
+
constructor(options: ResolvedOidcOptions, discovery: OidcDiscovery, tokens: OidcTokenVerifier);
|
|
52
|
+
/** The address the browser goes to for the registered code flow. */
|
|
53
|
+
authorization(input: OidcAuthorization): Promise<string>;
|
|
54
|
+
/** Exchanges a claimed code once, then verifies the ID token and answers the identity it asserts. */
|
|
55
|
+
exchange(input: OidcExchange): Promise<OidcExchangeResult>;
|
|
56
|
+
/** The RP-initiated logout address of the registered client. */
|
|
57
|
+
logout(input?: OidcLogout): Promise<string>;
|
|
58
|
+
}
|