@yielded/oauth 0.1.0-beta.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -0
- package/THIRD_PARTY_NOTICES.md +107 -0
- package/dist/Errors.d.mts +17 -0
- package/dist/Errors.mjs +18 -0
- package/dist/Errors.mjs.map +1 -0
- package/dist/OAuth.d.mts +122 -0
- package/dist/OAuth.mjs +261 -0
- package/dist/OAuth.mjs.map +1 -0
- package/dist/Oidc.d.mts +57 -0
- package/dist/Oidc.mjs +120 -0
- package/dist/Oidc.mjs.map +1 -0
- package/dist/Pkce.d.mts +12 -0
- package/dist/Pkce.mjs +28 -0
- package/dist/Pkce.mjs.map +1 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.mjs +5 -0
- package/dist/internal/ownership.mjs +21 -0
- package/dist/internal/ownership.mjs.map +1 -0
- package/dist/internal/transport.mjs +57 -0
- package/dist/internal/transport.mjs.map +1 -0
- package/dist/internal/validation.mjs +146 -0
- package/dist/internal/validation.mjs.map +1 -0
- package/package.json +1 -0
- package/src/Errors.ts +17 -0
- package/src/OAuth.ts +463 -0
- package/src/Oidc.ts +256 -0
- package/src/Pkce.ts +37 -0
- package/src/index.ts +4 -0
- package/src/internal/ownership.ts +30 -0
- package/src/internal/transport.ts +100 -0
- package/src/internal/validation.ts +236 -0
package/README.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# @yielded/oauth
|
|
2
|
+
|
|
3
|
+
Native Effect OAuth authorization-code and refresh flows, PKCE S256, and signed
|
|
4
|
+
RS256 OpenID Connect. Runtime dependencies are Effect and `@yielded/jose`.
|
|
5
|
+
|
|
6
|
+
Use `OAuth.make` for an installed client and `Oidc.makeVerifier` for its OIDC
|
|
7
|
+
verifier. Both require `HttpClient` and an owning `Scope`; keep that scope open
|
|
8
|
+
for their lifetime. `Pkce` requires Effect `Crypto`; verification requires
|
|
9
|
+
`Signature` from `@yielded/crypto` and Effect `Crypto`. Runtime Layers belong
|
|
10
|
+
to the application. `Oidc.discover` retrieves metadata from the exact trusted
|
|
11
|
+
issuer's OIDC discovery endpoint.
|
|
12
|
+
|
|
13
|
+
Closing either factory's scope cancels and joins its active operations; pending
|
|
14
|
+
results and subsequent calls fail with `OAuthUnavailable`. Caller interruption
|
|
15
|
+
cancels and joins that operation while leaving the client or verifier usable.
|
|
16
|
+
|
|
17
|
+
`client.codeGrant` and `client.refreshGrant` return private `TokenReceipt` values.
|
|
18
|
+
Inspect `Redacted.value(receipt.body)` for provider-specific receipt rules, then
|
|
19
|
+
call `OAuth.tokens`. Its tokens remain redacted. Raw expiry extensions and scope
|
|
20
|
+
strings are retained unchanged, including provider-specific comma scopes.
|
|
21
|
+
`OAuth.tokens` accepts `expires_in` as a finite nonnegative number or a whole
|
|
22
|
+
decimal string such as `"60.5"`; whitespace, numeric-prefix junk, exponent syntax,
|
|
23
|
+
`NaN` and infinity are rejected. Provider checks can still require a raw number.
|
|
24
|
+
A returned receipt has not established grant success or an authenticated identity.
|
|
25
|
+
For OIDC, require an ID token on initial exchange and call `verifier.verify` with
|
|
26
|
+
the captured `verificationStartedAt`, nonce, access token and authorization code.
|
|
27
|
+
It verifies the signature before claims, checks present `at_hash`/`c_hash`, and
|
|
28
|
+
returns redacted verified claims for application-owned identity/profile mapping.
|
|
29
|
+
On refresh, verify any returned ID token with `previous` subject/authentication
|
|
30
|
+
time; an omitted refresh ID token is an application-owned continuation.
|
|
31
|
+
|
|
32
|
+
The application owns registered callbacks, expected state and response issuer,
|
|
33
|
+
provider generations, permissions, identity callbacks, grant retention and durable
|
|
34
|
+
receipts. This package makes one request per exchange, refresh or revocation.
|
|
35
|
+
`OAuthRejected` identifies a complete HTTP 400 `invalid_grant` or an authenticated
|
|
36
|
+
claim rejection. Malformed responses and expected transport, key and signature
|
|
37
|
+
failures are `OAuthUnavailable`; they do not authorize repeating a possibly
|
|
38
|
+
committed request. Implementation defects, including those in injected services,
|
|
39
|
+
remain in Effect's defect channel.
|
|
40
|
+
|
|
41
|
+
Provider endpoints require HTTPS and exact response URLs. Redirects, cookies and
|
|
42
|
+
HTTP tracing are disabled for Fetch; custom HttpClients must preserve cancellation
|
|
43
|
+
and have no retry, redirect or cookie middleware. Installed profile endpoints and
|
|
44
|
+
headers are validated before credentials are sent. Responses are bounded to 1 MiB
|
|
45
|
+
by default (configurable downward). JSON is limited to 64 object/array levels and
|
|
46
|
+
65,536 values, including the root in both limits; excess depth or size fails with
|
|
47
|
+
`OAuthUnavailable`. Installed metadata and returned JSON snapshots are deeply
|
|
48
|
+
frozen. Requests to token/revocation endpoints are bounded to 128 KiB and
|
|
49
|
+
authorization URLs to 16 KiB. `timeoutMs` covers each request
|
|
50
|
+
through body consumption. Applications bound their complete multi-step workflow.
|
|
51
|
+
The current Fetch reference is captured when constructing the client/verifier.
|
|
52
|
+
|
|
53
|
+
Each verifier owns one scoped JOSE JWKS cache: at most 64 keys and 64 waiters, a
|
|
54
|
+
10-minute cache lifetime and a 30-second refresh cooldown. There is no global
|
|
55
|
+
issuer cache, background polling, stale-on-error fallback, or token-supplied key
|
|
56
|
+
endpoint. The supported profile requires advertised code, S256 and RS256 support;
|
|
57
|
+
other OAuth grants and ID-token signature algorithms are outside this profile.
|
|
58
|
+
|
|
59
|
+
See [third-party notices](THIRD_PARTY_NOTICES.md) for pinned upstream contract
|
|
60
|
+
sources and licenses.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Third-party notices
|
|
2
|
+
|
|
3
|
+
Selected OAuth/OIDC contracts were adapted into Effect tests from the pinned
|
|
4
|
+
upstream snapshots below. The native runtime uses Effect and owned JOSE, and
|
|
5
|
+
includes the attributed form-encoding adaptation below. Neither upstream SDK is
|
|
6
|
+
a runtime dependency.
|
|
7
|
+
|
|
8
|
+
The case mappings identify selected assertions and fixtures for Auth's
|
|
9
|
+
authorization-code + S256/RS256 profile. They do not represent complete ports of
|
|
10
|
+
the upstream suites. Local case names below are exact; paths are package-relative.
|
|
11
|
+
|
|
12
|
+
## openid-client 6.8.8
|
|
13
|
+
|
|
14
|
+
Version: **6.8.8**. Commit: **`04c59826121f736afc71fe005439c961d5fb6f3b`**.
|
|
15
|
+
|
|
16
|
+
| Pinned upstream file and case | Local adaptation |
|
|
17
|
+
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
18
|
+
| [test/token-response.test.ts](https://github.com/panva/openid-client/blob/04c59826121f736afc71fe005439c961d5fb6f3b/test/token-response.test.ts), `expiresIn returns zero for an already expired token` | [test/protocol.test.ts](test/protocol.test.ts), `distinguishes complete invalid_grant400 from ambiguous or malformed receipts`: zero-lifetime assertion only. |
|
|
19
|
+
|
|
20
|
+
The local `expiresIn` value is the decoded lifetime; the upstream helper's
|
|
21
|
+
elapsed-time/countdown implementation is not included.
|
|
22
|
+
|
|
23
|
+
```text
|
|
24
|
+
The MIT License (MIT)
|
|
25
|
+
|
|
26
|
+
Copyright (c) 2016 Filip Skokan
|
|
27
|
+
|
|
28
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
29
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
30
|
+
in the Software without restriction, including without limitation the rights
|
|
31
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
32
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
33
|
+
furnished to do so, subject to the following conditions:
|
|
34
|
+
|
|
35
|
+
The above copyright notice and this permission notice shall be included in all
|
|
36
|
+
copies or substantial portions of the Software.
|
|
37
|
+
|
|
38
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
39
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
40
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
41
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
42
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
43
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
44
|
+
SOFTWARE.
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## oauth4webapi 3.8.8
|
|
49
|
+
|
|
50
|
+
Version: **3.8.8**. Commit: **`916b97952dbf431d8b72f369840de54f5a286e4d`**.
|
|
51
|
+
|
|
52
|
+
Runtime adaptation: [src/index.ts, `formUrlEncode`](https://github.com/panva/oauth4webapi/blob/916b97952dbf431d8b72f369840de54f5a286e4d/src/index.ts#L1867),
|
|
53
|
+
used by upstream `ClientSecretBasic`, is adapted as
|
|
54
|
+
[src/OAuth.ts, `basicComponent`](src/OAuth.ts). It form-encodes each credential
|
|
55
|
+
component before joining them with a colon, including escaped punctuation and
|
|
56
|
+
`+` for spaces. The MIT license below covers this adaptation as well as the
|
|
57
|
+
selected test contracts.
|
|
58
|
+
|
|
59
|
+
| Pinned upstream file and cases | Local adaptation |
|
|
60
|
+
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
61
|
+
| [test/client_auth.test.ts](https://github.com/panva/oauth4webapi/blob/916b97952dbf431d8b72f369840de54f5a286e4d/test/client_auth.test.ts): `client_secret_basic`, `client_secret_basic (appendix b)`, `client_secret_post`, `none` | [test/protocol.test.ts](test/protocol.test.ts): `authenticates Basic/Post/None once and preserves private raw provider receipts`, including the Appendix B punctuation fixture. |
|
|
62
|
+
| [test/discovery.test.ts](https://github.com/panva/oauth4webapi/blob/916b97952dbf431d8b72f369840de54f5a286e4d/test/discovery.test.ts): `discoveryRequest() - oidc with a pathname`, `processDiscoveryResponse()` | [test/protocol.test.ts](test/protocol.test.ts): `discovers the exact issuer including its trailing slash and rejects substituted metadata`, selecting pathname and exact-issuer assertions. |
|
|
63
|
+
| [test/authorization_code.test.ts](https://github.com/panva/oauth4webapi/blob/916b97952dbf431d8b72f369840de54f5a286e4d/test/authorization_code.test.ts): `authorizationCodeGrantRequest()`, `authorizationCodeGrantRequest() w/ Extra Parameters`, `processAuthorizationCodeResponse()` | [test/protocol.test.ts](test/protocol.test.ts): `authenticates Basic/Post/None once and preserves private raw provider receipts`; `distinguishes complete invalid_grant400 from ambiguous or malformed receipts`, selecting request fields, extra parameters, token values and malformed-response checks. |
|
|
64
|
+
| [test/refresh_token.test.ts](https://github.com/panva/oauth4webapi/blob/916b97952dbf431d8b72f369840de54f5a286e4d/test/refresh_token.test.ts): `refreshTokenGrantRequest()`, `processRefreshTokenResponse() without ID Tokens` | [test/protocol.test.ts](test/protocol.test.ts): `refreshes, fetches the installed profile and revokes with separately installed credentials`, selecting refresh request fields and token receipt parsing. |
|
|
65
|
+
| [test/revocation.test.ts](https://github.com/panva/oauth4webapi/blob/916b97952dbf431d8b72f369840de54f5a286e4d/test/revocation.test.ts): `revocationRequest()`, `processRevocationResponse()` | [test/protocol.test.ts](test/protocol.test.ts): `refreshes, fetches the installed profile and revokes with separately installed credentials`; `bounds bodies, refuses redirects and unexpected URLs, and never repeats failed exchange/revocation`, selecting request fields, empty HTTP 200 success and failed responses. |
|
|
66
|
+
| [test/authorization_code.test.ts](https://github.com/panva/oauth4webapi/blob/916b97952dbf431d8b72f369840de54f5a286e4d/test/authorization_code.test.ts): `processAuthorizationCodeResponse() nonce checks`, `processAuthorizationCodeResponse() auth_time checks`, `processAuthorizationCodeResponse() azp checks` | [test/oidc.test.ts](test/oidc.test.ts): `rejects authenticated wrong issuer/aud/azp/time/nonce/hash claims with no clock tolerance`, selecting nonce, authentication-age and authorized-party checks. |
|
|
67
|
+
| [test/auth_time.test.ts](https://github.com/panva/oauth4webapi/blob/916b97952dbf431d8b72f369840de54f5a286e4d/test/auth_time.test.ts): `authorization code auth_time with ${JSON.stringify(options)}` | [test/oidc.test.ts](test/oidc.test.ts): `rejects authenticated wrong issuer/aud/azp/time/nonce/hash claims with no clock tolerance`, selecting the maximum-age rejection. The upstream explicit-skip variants are not ported. |
|
|
68
|
+
| [test/jwt_claims.test.ts](https://github.com/panva/oauth4webapi/blob/916b97952dbf431d8b72f369840de54f5a286e4d/test/jwt_claims.test.ts): `${process.name}() validates required ID Token claims`, `${name} validates required ID Token claims`, `${name} ID Token policy checks` | [test/oidc.test.ts](test/oidc.test.ts): `verifies signed OIDC claims/hashes, retains profile extensions and clamps authentication time to captured start`; `rejects authenticated wrong issuer/aud/azp/time/nonce/hash claims with no clock tolerance`; `verifies signatures before definite claim rejection and keeps key/signature faults ambiguous`. These adapt claim fixtures and selected nonce/azp/signature checks to signed RS256 tokens; upstream grant, hybrid and detached-signature matrices are not ported. |
|
|
69
|
+
|
|
70
|
+
The PKCE verifier/challenge vector in [test/protocol.test.ts](test/protocol.test.ts),
|
|
71
|
+
`uses RFC7636 S256 and fresh private 32-byte transaction secrets`, comes from
|
|
72
|
+
[RFC7636 Appendix B](https://www.rfc-editor.org/rfc/rfc7636.html#appendix-B).
|
|
73
|
+
|
|
74
|
+
Auth supplies the strict single-audience policy, signature-before-rejection
|
|
75
|
+
classification, hash bindings, captured authentication time, refresh continuity,
|
|
76
|
+
private receipts, no retries and scoped cleanup. Refresh continuity in
|
|
77
|
+
`refresh binds subject/optional nonce/auth_time without treating old max_age as a new login`
|
|
78
|
+
is an Auth-profile case guided by
|
|
79
|
+
[OpenID Connect Core 12.2](https://openid.net/specs/openid-connect-core-1_0.html#RefreshTokenResponse).
|
|
80
|
+
The cancellation seam came from this repository's former
|
|
81
|
+
`packages/auth-openid-client/test/protocol.test.ts`. Metadata immutability,
|
|
82
|
+
bounded JSON and close-active-operation regressions are local cases.
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
The MIT License (MIT)
|
|
86
|
+
|
|
87
|
+
Copyright (c) 2022 Filip Skokan
|
|
88
|
+
|
|
89
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
90
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
91
|
+
in the Software without restriction, including without limitation the rights
|
|
92
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
93
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
94
|
+
furnished to do so, subject to the following conditions:
|
|
95
|
+
|
|
96
|
+
The above copyright notice and this permission notice shall be included in all
|
|
97
|
+
copies or substantial portions of the Software.
|
|
98
|
+
|
|
99
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
100
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
101
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
102
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
103
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
104
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
105
|
+
SOFTWARE.
|
|
106
|
+
|
|
107
|
+
```
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
//#region src/Errors.d.ts
|
|
3
|
+
declare const ConfigurationError_base: Schema.Class<ConfigurationError, Schema.TaggedStruct<"OAuthConfigurationError", {
|
|
4
|
+
readonly reason: Schema.Literals<readonly ["issuer", "metadata", "authentication", "parameters", "endpoint"]>;
|
|
5
|
+
}>, import("effect/Cause").YieldableError>;
|
|
6
|
+
/** Local protocol configuration is invalid; no remote response or credential is retained. */
|
|
7
|
+
export declare class ConfigurationError extends ConfigurationError_base {}
|
|
8
|
+
declare const Rejected_base: Schema.Class<Rejected, Schema.TaggedStruct<"OAuthRejected", {
|
|
9
|
+
readonly reason: Schema.Literals<readonly ["invalid_grant", "claims"]>;
|
|
10
|
+
}>, import("effect/Cause").YieldableError>;
|
|
11
|
+
/** A definite, authenticated claim rejection or a complete HTTP 400 invalid_grant. */
|
|
12
|
+
export declare class Rejected extends Rejected_base {}
|
|
13
|
+
declare const Unavailable_base: Schema.Class<Unavailable, Schema.TaggedStruct<"OAuthUnavailable", {}>, import("effect/Cause").YieldableError>;
|
|
14
|
+
/** The outcome is unknown. This error never authorizes retrying a grant. */
|
|
15
|
+
export declare class Unavailable extends Unavailable_base {}
|
|
16
|
+
//#endregion
|
|
17
|
+
//# sourceMappingURL=Errors.d.mts.map
|
package/dist/Errors.mjs
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Schema } from "effect";
|
|
2
|
+
//#region src/Errors.ts
|
|
3
|
+
/** Local protocol configuration is invalid; no remote response or credential is retained. */
|
|
4
|
+
var ConfigurationError = class extends Schema.TaggedError()("OAuthConfigurationError", { reason: Schema.Literals([
|
|
5
|
+
"issuer",
|
|
6
|
+
"metadata",
|
|
7
|
+
"authentication",
|
|
8
|
+
"parameters",
|
|
9
|
+
"endpoint"
|
|
10
|
+
]) }) {};
|
|
11
|
+
/** A definite, authenticated claim rejection or a complete HTTP 400 invalid_grant. */
|
|
12
|
+
var Rejected = class extends Schema.TaggedError()("OAuthRejected", { reason: Schema.Literals(["invalid_grant", "claims"]) }) {};
|
|
13
|
+
/** The outcome is unknown. This error never authorizes retrying a grant. */
|
|
14
|
+
var Unavailable = class extends Schema.TaggedError()("OAuthUnavailable", {}) {};
|
|
15
|
+
//#endregion
|
|
16
|
+
export { ConfigurationError, Rejected, Unavailable };
|
|
17
|
+
|
|
18
|
+
//# sourceMappingURL=Errors.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Errors.mjs","names":[],"sources":["../src/Errors.ts"],"sourcesContent":["import { Schema } from \"effect\";\n\n/** Local protocol configuration is invalid; no remote response or credential is retained. */\nexport class ConfigurationError extends Schema.TaggedError<ConfigurationError>()(\n \"OAuthConfigurationError\",\n {\n reason: Schema.Literals([\"issuer\", \"metadata\", \"authentication\", \"parameters\", \"endpoint\"]),\n },\n) {}\n\n/** A definite, authenticated claim rejection or a complete HTTP 400 invalid_grant. */\nexport class Rejected extends Schema.TaggedError<Rejected>()(\"OAuthRejected\", {\n reason: Schema.Literals([\"invalid_grant\", \"claims\"]),\n}) {}\n\n/** The outcome is unknown. This error never authorizes retrying a grant. */\nexport class Unavailable extends Schema.TaggedError<Unavailable>()(\"OAuthUnavailable\", {}) {}\n"],"mappings":";;;AAGA,IAAa,qBAAb,cAAwC,OAAO,YAAgC,CAAC,CAC9E,2BACA,EACE,QAAQ,OAAO,SAAS;CAAC;CAAU;CAAY;CAAkB;CAAc;AAAU,CAAC,EAC5F,CACF,CAAC,CAAC,CAAC;;AAGH,IAAa,WAAb,cAA8B,OAAO,YAAsB,CAAC,CAAC,iBAAiB,EAC5E,QAAQ,OAAO,SAAS,CAAC,iBAAiB,QAAQ,CAAC,EACrD,CAAC,CAAC,CAAC,CAAC;;AAGJ,IAAa,cAAb,cAAiC,OAAO,YAAyB,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC,CAAC"}
|
package/dist/OAuth.d.mts
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { ConfigurationError, Rejected, Unavailable } from "./Errors.mjs";
|
|
2
|
+
import { Effect, Redacted, Schema, Scope } from "effect";
|
|
3
|
+
import { HttpClient } from "effect/http";
|
|
4
|
+
//#region src/OAuth.d.ts
|
|
5
|
+
export declare const JsonObject: Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>;
|
|
6
|
+
export type JsonObject = typeof JsonObject.Type;
|
|
7
|
+
export declare const Metadata: Schema.Struct<{
|
|
8
|
+
readonly issuer: Schema.NonEmptyString;
|
|
9
|
+
readonly authorization_endpoint: Schema.NonEmptyString;
|
|
10
|
+
readonly token_endpoint: Schema.NonEmptyString;
|
|
11
|
+
readonly jwks_uri: Schema.optionalKey<Schema.NonEmptyString>;
|
|
12
|
+
readonly userinfo_endpoint: Schema.optionalKey<Schema.NonEmptyString>;
|
|
13
|
+
readonly revocation_endpoint: Schema.optionalKey<Schema.NonEmptyString>;
|
|
14
|
+
readonly code_challenge_methods_supported: Schema.optionalKey<Schema.$Array<Schema.NonEmptyString>>;
|
|
15
|
+
readonly response_types_supported: Schema.optionalKey<Schema.$Array<Schema.NonEmptyString>>;
|
|
16
|
+
readonly grant_types_supported: Schema.optionalKey<Schema.$Array<Schema.NonEmptyString>>;
|
|
17
|
+
readonly id_token_signing_alg_values_supported: Schema.optionalKey<Schema.$Array<Schema.NonEmptyString>>;
|
|
18
|
+
readonly token_endpoint_auth_methods_supported: Schema.optionalKey<Schema.$Array<Schema.NonEmptyString>>;
|
|
19
|
+
readonly revocation_endpoint_auth_methods_supported: Schema.optionalKey<Schema.$Array<Schema.NonEmptyString>>;
|
|
20
|
+
readonly authorization_response_iss_parameter_supported: Schema.optionalKey<Schema.Boolean>;
|
|
21
|
+
}>;
|
|
22
|
+
export type Metadata = typeof Metadata.Type;
|
|
23
|
+
export declare const Authentication: Schema.Union<readonly [Schema.Struct<{
|
|
24
|
+
readonly method: Schema.Literal<"client_secret_basic">;
|
|
25
|
+
readonly secret: Schema.Redacted<Schema.NonEmptyString>;
|
|
26
|
+
}>, Schema.Struct<{
|
|
27
|
+
readonly method: Schema.Literal<"client_secret_post">;
|
|
28
|
+
readonly secret: Schema.Redacted<Schema.NonEmptyString>;
|
|
29
|
+
}>, Schema.Struct<{
|
|
30
|
+
readonly method: Schema.Literal<"none">;
|
|
31
|
+
readonly publicClient: Schema.Literal<true>;
|
|
32
|
+
}>]>;
|
|
33
|
+
export type Authentication = typeof Authentication.Type;
|
|
34
|
+
/** Uninterpreted private receipt. Inspect provider extensions before calling tokens. */
|
|
35
|
+
export declare const TokenReceipt: Schema.Struct<{
|
|
36
|
+
readonly status: Schema.Int;
|
|
37
|
+
readonly contentType: Schema.NullOr<Schema.String>;
|
|
38
|
+
readonly body: Schema.Redacted<Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>>;
|
|
39
|
+
}>;
|
|
40
|
+
export type TokenReceipt = typeof TokenReceipt.Type;
|
|
41
|
+
export declare const TokenSet: Schema.Struct<{
|
|
42
|
+
readonly tokenType: Schema.Literal<"bearer">;
|
|
43
|
+
readonly accessToken: Schema.Redacted<Schema.NonEmptyString>;
|
|
44
|
+
readonly refreshToken: Schema.optionalKey<Schema.Redacted<Schema.NonEmptyString>>;
|
|
45
|
+
readonly idToken: Schema.optionalKey<Schema.Redacted<Schema.NonEmptyString>>;
|
|
46
|
+
readonly expiresIn: Schema.optionalKey<Schema.Finite>;
|
|
47
|
+
readonly scope: Schema.optionalKey<Schema.String>;
|
|
48
|
+
}>;
|
|
49
|
+
export type TokenSet = typeof TokenSet.Type;
|
|
50
|
+
export interface RequestOptions {
|
|
51
|
+
readonly timeoutMs: number;
|
|
52
|
+
readonly maxResponseBytes?: number;
|
|
53
|
+
}
|
|
54
|
+
export interface ClientOptions extends RequestOptions {
|
|
55
|
+
readonly metadata: Metadata;
|
|
56
|
+
readonly clientId: string;
|
|
57
|
+
readonly authentication: Authentication;
|
|
58
|
+
readonly profile?: {
|
|
59
|
+
readonly url: string;
|
|
60
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
61
|
+
};
|
|
62
|
+
readonly revocationAuthentication?: Authentication;
|
|
63
|
+
}
|
|
64
|
+
export interface Parameters {
|
|
65
|
+
readonly parameters?: Readonly<Record<string, string>>;
|
|
66
|
+
readonly resources?: ReadonlyArray<string>;
|
|
67
|
+
}
|
|
68
|
+
export interface AuthorizationInput extends Parameters {
|
|
69
|
+
readonly redirectUri: string;
|
|
70
|
+
readonly scopes: ReadonlyArray<string>;
|
|
71
|
+
readonly state: Redacted.Redacted<string>;
|
|
72
|
+
readonly codeChallenge: string;
|
|
73
|
+
readonly nonce?: Redacted.Redacted<string>;
|
|
74
|
+
readonly maxAgeSeconds?: number;
|
|
75
|
+
}
|
|
76
|
+
export interface CodeGrantInput extends Parameters {
|
|
77
|
+
readonly code: Redacted.Redacted<string>;
|
|
78
|
+
readonly redirectUri: string;
|
|
79
|
+
readonly pkceVerifier: Redacted.Redacted<string>;
|
|
80
|
+
}
|
|
81
|
+
export interface RefreshGrantInput extends Parameters {
|
|
82
|
+
readonly refreshToken: Redacted.Redacted<string>;
|
|
83
|
+
readonly scopes?: ReadonlyArray<string>;
|
|
84
|
+
}
|
|
85
|
+
export interface RevocationInput {
|
|
86
|
+
readonly token: Redacted.Redacted<string>;
|
|
87
|
+
readonly tokenTypeHint: "access_token" | "refresh_token";
|
|
88
|
+
}
|
|
89
|
+
export interface Client {
|
|
90
|
+
readonly metadata: Metadata;
|
|
91
|
+
readonly authorizationUrl: (input: AuthorizationInput) => Effect.Effect<Redacted.Redacted<string>, ConfigurationError | Unavailable>;
|
|
92
|
+
readonly codeGrant: (input: CodeGrantInput) => Effect.Effect<TokenReceipt, ConfigurationError | Unavailable>;
|
|
93
|
+
readonly refreshGrant: (input: RefreshGrantInput) => Effect.Effect<TokenReceipt, ConfigurationError | Unavailable>;
|
|
94
|
+
readonly fetchProfile: (accessToken: Redacted.Redacted<string>) => Effect.Effect<JsonObject, ConfigurationError | Unavailable>;
|
|
95
|
+
readonly revoke: (input: RevocationInput) => Effect.Effect<void, ConfigurationError | Unavailable>;
|
|
96
|
+
}
|
|
97
|
+
/** Parse once provider-specific receipt checks have succeeded. expires_in accepts
|
|
98
|
+
* finite nonnegative numbers and whole decimal strings (for example "60.5").
|
|
99
|
+
* Whitespace, exponent/prefix syntax and nonfinite values are rejected. Raw
|
|
100
|
+
* receipt extensions/scopes are untouched; no request, retry or claim verification. */
|
|
101
|
+
export declare const tokens: (input: {
|
|
102
|
+
readonly status: number;
|
|
103
|
+
readonly contentType: string | null;
|
|
104
|
+
readonly body: Redacted.Redacted<{
|
|
105
|
+
readonly [x: string]: Schema.Json;
|
|
106
|
+
}>;
|
|
107
|
+
}) => Effect.Effect<{
|
|
108
|
+
readonly tokenType: "bearer";
|
|
109
|
+
readonly accessToken: Redacted.Redacted<string>;
|
|
110
|
+
readonly refreshToken?: Redacted.Redacted<string>;
|
|
111
|
+
readonly idToken?: Redacted.Redacted<string>;
|
|
112
|
+
readonly expiresIn?: number;
|
|
113
|
+
readonly scope?: string;
|
|
114
|
+
}, Rejected | Unavailable, never>;
|
|
115
|
+
/** One installed client, exact endpoints, private credentials and owning Scope.
|
|
116
|
+
* Supply an HttpClient without retry/redirect/cookie middleware. Fetch injection
|
|
117
|
+
* active at construction is captured. Owner closure cancels and joins active
|
|
118
|
+
* operations, then rejects their results as unavailable; caller interruption
|
|
119
|
+
* cancels and joins its operation without closing the client. */
|
|
120
|
+
export declare const make: (input: ClientOptions) => Effect.Effect<Client, ConfigurationError | Unavailable, HttpClient.HttpClient | Scope.Scope>;
|
|
121
|
+
//#endregion
|
|
122
|
+
//# sourceMappingURL=OAuth.d.mts.map
|
package/dist/OAuth.mjs
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { ConfigurationError, Rejected, Unavailable } from "./Errors.mjs";
|
|
2
|
+
import { make as make$1 } from "./internal/ownership.mjs";
|
|
3
|
+
import { Callback, Challenge, Endpoint, Headers, Issuer, JsonObject as JsonObject$1, Parameters, ProtocolEndpoint, RequestOptions, Resources, Scopes, Verifier, configuration, decode, freeze, integer, reveal, secret, text } from "./internal/validation.mjs";
|
|
4
|
+
import { capture, isJson, json, request } from "./internal/transport.mjs";
|
|
5
|
+
import { Effect, Redacted, Schema } from "effect";
|
|
6
|
+
import { Base64 } from "effect/encoding";
|
|
7
|
+
import { HttpClientRequest } from "effect/http";
|
|
8
|
+
//#region src/OAuth.ts
|
|
9
|
+
const JsonObject = JsonObject$1;
|
|
10
|
+
const Metadata = Schema.Struct({
|
|
11
|
+
issuer: Issuer,
|
|
12
|
+
authorization_endpoint: ProtocolEndpoint,
|
|
13
|
+
token_endpoint: ProtocolEndpoint,
|
|
14
|
+
jwks_uri: Schema.optionalKey(Endpoint),
|
|
15
|
+
userinfo_endpoint: Schema.optionalKey(Endpoint),
|
|
16
|
+
revocation_endpoint: Schema.optionalKey(ProtocolEndpoint),
|
|
17
|
+
code_challenge_methods_supported: Schema.optionalKey(Schema.Array(text(64)).check(Schema.isMaxLength(64))),
|
|
18
|
+
response_types_supported: Schema.optionalKey(Schema.Array(text(64)).check(Schema.isMaxLength(64))),
|
|
19
|
+
grant_types_supported: Schema.optionalKey(Schema.Array(text(128)).check(Schema.isMaxLength(64))),
|
|
20
|
+
id_token_signing_alg_values_supported: Schema.optionalKey(Schema.Array(text(64)).check(Schema.isMaxLength(64))),
|
|
21
|
+
token_endpoint_auth_methods_supported: Schema.optionalKey(Schema.Array(text(64)).check(Schema.isMaxLength(64))),
|
|
22
|
+
revocation_endpoint_auth_methods_supported: Schema.optionalKey(Schema.Array(text(64)).check(Schema.isMaxLength(64))),
|
|
23
|
+
authorization_response_iss_parameter_supported: Schema.optionalKey(Schema.Boolean)
|
|
24
|
+
});
|
|
25
|
+
const Authentication = Schema.Union([
|
|
26
|
+
Schema.Struct({
|
|
27
|
+
method: Schema.Literal("client_secret_basic"),
|
|
28
|
+
secret: secret(4096)
|
|
29
|
+
}),
|
|
30
|
+
Schema.Struct({
|
|
31
|
+
method: Schema.Literal("client_secret_post"),
|
|
32
|
+
secret: secret(4096)
|
|
33
|
+
}),
|
|
34
|
+
Schema.Struct({
|
|
35
|
+
method: Schema.Literal("none"),
|
|
36
|
+
publicClient: Schema.Literal(true)
|
|
37
|
+
})
|
|
38
|
+
]);
|
|
39
|
+
/** Uninterpreted private receipt. Inspect provider extensions before calling tokens. */
|
|
40
|
+
const TokenReceipt = Schema.Struct({
|
|
41
|
+
status: integer(100, 599),
|
|
42
|
+
contentType: Schema.NullOr(Schema.String.check(Schema.isMaxLength(512))),
|
|
43
|
+
body: Schema.Redacted(JsonObject, { disallowJsonEncode: true })
|
|
44
|
+
});
|
|
45
|
+
const TokenSet = Schema.Struct({
|
|
46
|
+
tokenType: Schema.Literal("bearer"),
|
|
47
|
+
accessToken: secret(16384),
|
|
48
|
+
refreshToken: Schema.optionalKey(secret(16384)),
|
|
49
|
+
idToken: Schema.optionalKey(secret(65536)),
|
|
50
|
+
expiresIn: Schema.optionalKey(Schema.Finite.check(Schema.isGreaterThanOrEqualTo(0))),
|
|
51
|
+
scope: Schema.optionalKey(Schema.String.check(Schema.isMaxLength(16447)))
|
|
52
|
+
});
|
|
53
|
+
const ClientOptionsSchema = Schema.Struct({
|
|
54
|
+
metadata: Metadata,
|
|
55
|
+
clientId: text(1024),
|
|
56
|
+
authentication: Authentication,
|
|
57
|
+
...RequestOptions.fields,
|
|
58
|
+
profile: Schema.optionalKey(Schema.Struct({
|
|
59
|
+
url: Endpoint,
|
|
60
|
+
headers: Schema.optionalKey(Headers)
|
|
61
|
+
})),
|
|
62
|
+
revocationAuthentication: Schema.optionalKey(Authentication)
|
|
63
|
+
});
|
|
64
|
+
const additional = {
|
|
65
|
+
parameters: Schema.optionalKey(Parameters),
|
|
66
|
+
resources: Schema.optionalKey(Resources)
|
|
67
|
+
};
|
|
68
|
+
const Authorization = Schema.Struct({
|
|
69
|
+
...additional,
|
|
70
|
+
redirectUri: Callback,
|
|
71
|
+
scopes: Scopes,
|
|
72
|
+
state: secret(256),
|
|
73
|
+
codeChallenge: Challenge,
|
|
74
|
+
nonce: Schema.optionalKey(secret(256)),
|
|
75
|
+
maxAgeSeconds: Schema.optionalKey(integer(0, 86400))
|
|
76
|
+
});
|
|
77
|
+
const CodeGrant = Schema.Struct({
|
|
78
|
+
...additional,
|
|
79
|
+
code: secret(16384),
|
|
80
|
+
redirectUri: Callback,
|
|
81
|
+
pkceVerifier: Verifier
|
|
82
|
+
});
|
|
83
|
+
const RefreshGrant = Schema.Struct({
|
|
84
|
+
...additional,
|
|
85
|
+
refreshToken: secret(16384),
|
|
86
|
+
scopes: Schema.optionalKey(Scopes)
|
|
87
|
+
});
|
|
88
|
+
const Revocation = Schema.Struct({
|
|
89
|
+
token: secret(16384),
|
|
90
|
+
tokenTypeHint: Schema.Literals(["access_token", "refresh_token"])
|
|
91
|
+
});
|
|
92
|
+
const ExpiresIn = Schema.Union([Schema.Finite, Schema.String.check(Schema.isPattern(/^(?:0|[1-9][0-9]*)(?:\.[0-9]+)?$/u)).pipe(Schema.decodeTo(Schema.FiniteFromString))]).check(Schema.isGreaterThanOrEqualTo(0));
|
|
93
|
+
const RawToken = Schema.Struct({
|
|
94
|
+
access_token: text(16384),
|
|
95
|
+
token_type: Schema.String.check(Schema.isPattern(/^[Bb][Ee][Aa][Rr][Ee][Rr]$/)),
|
|
96
|
+
refresh_token: Schema.optionalKey(text(16384)),
|
|
97
|
+
id_token: Schema.optionalKey(text(65536)),
|
|
98
|
+
expires_in: Schema.optionalKey(ExpiresIn),
|
|
99
|
+
scope: Schema.optionalKey(Schema.String.check(Schema.isMaxLength(16447)))
|
|
100
|
+
});
|
|
101
|
+
const Terminal = Schema.Struct({
|
|
102
|
+
error: text(128),
|
|
103
|
+
error_description: Schema.optionalKey(Schema.String.check(Schema.isMaxLength(4096))),
|
|
104
|
+
error_uri: Schema.optionalKey(Schema.String.check(Schema.isMaxLength(2048)))
|
|
105
|
+
});
|
|
106
|
+
const successFields = [
|
|
107
|
+
"access_token",
|
|
108
|
+
"refresh_token",
|
|
109
|
+
"id_token",
|
|
110
|
+
"token_type",
|
|
111
|
+
"scope",
|
|
112
|
+
"expires_in",
|
|
113
|
+
"refresh_expires_in",
|
|
114
|
+
"refresh_token_expires_in"
|
|
115
|
+
];
|
|
116
|
+
/** Parse once provider-specific receipt checks have succeeded. expires_in accepts
|
|
117
|
+
* finite nonnegative numbers and whole decimal strings (for example "60.5").
|
|
118
|
+
* Whitespace, exponent/prefix syntax and nonfinite values are rejected. Raw
|
|
119
|
+
* receipt extensions/scopes are untouched; no request, retry or claim verification. */
|
|
120
|
+
const tokens = Effect.fnUntraced(function* (input) {
|
|
121
|
+
const receipt = yield* decode(TokenReceipt, input);
|
|
122
|
+
if (!isJson(receipt.contentType)) return yield* Unavailable.make({});
|
|
123
|
+
const raw = yield* reveal(receipt.body);
|
|
124
|
+
if (Object.hasOwn(raw, "error")) {
|
|
125
|
+
if (successFields.some((key) => Object.hasOwn(raw, key))) return yield* Unavailable.make({});
|
|
126
|
+
const terminal = yield* decode(Terminal, raw);
|
|
127
|
+
if (receipt.status === 400 && terminal.error === "invalid_grant") return yield* Rejected.make({ reason: "invalid_grant" });
|
|
128
|
+
return yield* Unavailable.make({});
|
|
129
|
+
}
|
|
130
|
+
if (receipt.status !== 200 || Object.hasOwn(raw, "error_description") || Object.hasOwn(raw, "error_uri")) return yield* Unavailable.make({});
|
|
131
|
+
const value = yield* decode(RawToken, raw);
|
|
132
|
+
return {
|
|
133
|
+
tokenType: "bearer",
|
|
134
|
+
accessToken: Redacted.make(value.access_token),
|
|
135
|
+
...value.refresh_token === void 0 ? {} : { refreshToken: Redacted.make(value.refresh_token) },
|
|
136
|
+
...value.id_token === void 0 ? {} : { idToken: Redacted.make(value.id_token) },
|
|
137
|
+
...value.expires_in === void 0 ? {} : { expiresIn: value.expires_in },
|
|
138
|
+
...value.scope === void 0 ? {} : { scope: value.scope }
|
|
139
|
+
};
|
|
140
|
+
});
|
|
141
|
+
const parameters = (input) => {
|
|
142
|
+
const values = new URLSearchParams(input.parameters);
|
|
143
|
+
for (const resource of input.resources ?? []) values.append("resource", resource);
|
|
144
|
+
return values;
|
|
145
|
+
};
|
|
146
|
+
const basicComponent = (value) => encodeURIComponent(value).replace(/[-_.!~*'()]|%20/gu, (part) => part === "%20" ? "+" : `%${part.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
147
|
+
const authenticated = Effect.fnUntraced(function* (url, clientId, authentication, body) {
|
|
148
|
+
let request = HttpClientRequest.post(url).pipe(HttpClientRequest.acceptJson);
|
|
149
|
+
if (authentication.method === "client_secret_basic") {
|
|
150
|
+
const secret = yield* reveal(authentication.secret);
|
|
151
|
+
const credential = yield* Effect.try({
|
|
152
|
+
try: () => Base64.encode(`${basicComponent(clientId)}:${basicComponent(secret)}`),
|
|
153
|
+
catch: () => Unavailable.make({})
|
|
154
|
+
});
|
|
155
|
+
request = HttpClientRequest.setHeader(request, "authorization", `Basic ${credential}`);
|
|
156
|
+
} else {
|
|
157
|
+
body.set("client_id", clientId);
|
|
158
|
+
if (authentication.method === "client_secret_post") body.set("client_secret", yield* reveal(authentication.secret));
|
|
159
|
+
}
|
|
160
|
+
const encoded = body.toString();
|
|
161
|
+
if (new TextEncoder().encode(encoded).length > 131072) return yield* ConfigurationError.make({ reason: "parameters" });
|
|
162
|
+
return HttpClientRequest.bodyText(request, encoded, "application/x-www-form-urlencoded;charset=UTF-8");
|
|
163
|
+
});
|
|
164
|
+
/** One installed client, exact endpoints, private credentials and owning Scope.
|
|
165
|
+
* Supply an HttpClient without retry/redirect/cookie middleware. Fetch injection
|
|
166
|
+
* active at construction is captured. Owner closure cancels and joins active
|
|
167
|
+
* operations, then rejects their results as unavailable; caller interruption
|
|
168
|
+
* cancels and joins its operation without closing the client. */
|
|
169
|
+
const make = Effect.fnUntraced(function* (input) {
|
|
170
|
+
const options = yield* configuration(ClientOptionsSchema, input, "metadata");
|
|
171
|
+
const authentication = yield* detachAuthentication(options.authentication);
|
|
172
|
+
const revocationAuthentication = options.revocationAuthentication === void 0 ? authentication : yield* detachAuthentication(options.revocationAuthentication);
|
|
173
|
+
const metadata = freeze(options.metadata);
|
|
174
|
+
const supported = metadata.token_endpoint_auth_methods_supported ?? (metadata.jwks_uri === void 0 ? void 0 : ["client_secret_basic"]);
|
|
175
|
+
const revokeSupported = metadata.revocation_endpoint_auth_methods_supported ?? (metadata.jwks_uri === void 0 ? void 0 : ["client_secret_basic"]);
|
|
176
|
+
if (supported !== void 0 && !supported.includes(authentication.method) || options.revocationAuthentication !== void 0 && metadata.revocation_endpoint !== void 0 && revokeSupported !== void 0 && !revokeSupported.includes(revocationAuthentication.method)) return yield* ConfigurationError.make({ reason: "authentication" });
|
|
177
|
+
const { available, use } = yield* make$1;
|
|
178
|
+
const http = yield* capture;
|
|
179
|
+
const profile = options.profile === void 0 ? void 0 : freeze(options.profile);
|
|
180
|
+
yield* Effect.addFinalizer(() => Effect.sync(() => {
|
|
181
|
+
if (authentication.method !== "none") Redacted.wipeUnsafe(authentication.secret);
|
|
182
|
+
if (revocationAuthentication.method !== "none") Redacted.wipeUnsafe(revocationAuthentication.secret);
|
|
183
|
+
}));
|
|
184
|
+
const send = Effect.fnUntraced(function* (body) {
|
|
185
|
+
yield* available;
|
|
186
|
+
const request$1 = yield* authenticated(new URL(metadata.token_endpoint).href, options.clientId, authentication, body);
|
|
187
|
+
return yield* json(yield* request(http, request$1, options));
|
|
188
|
+
});
|
|
189
|
+
return {
|
|
190
|
+
metadata,
|
|
191
|
+
authorizationUrl: Effect.fnUntraced(function* (input) {
|
|
192
|
+
yield* available;
|
|
193
|
+
const value = yield* configuration(Authorization, input);
|
|
194
|
+
const url = new URL(metadata.authorization_endpoint);
|
|
195
|
+
const body = parameters(value);
|
|
196
|
+
body.set("client_id", options.clientId);
|
|
197
|
+
body.set("redirect_uri", value.redirectUri);
|
|
198
|
+
body.set("response_type", "code");
|
|
199
|
+
body.set("response_mode", "query");
|
|
200
|
+
body.set("scope", value.scopes.join(" "));
|
|
201
|
+
body.set("state", yield* reveal(value.state));
|
|
202
|
+
body.set("code_challenge", value.codeChallenge);
|
|
203
|
+
body.set("code_challenge_method", "S256");
|
|
204
|
+
if (value.nonce !== void 0) body.set("nonce", yield* reveal(value.nonce));
|
|
205
|
+
if (value.maxAgeSeconds !== void 0) body.set("max_age", String(value.maxAgeSeconds));
|
|
206
|
+
for (const [key, item] of body) url.searchParams.append(key, item);
|
|
207
|
+
if (url.href.length > 16384) return yield* ConfigurationError.make({ reason: "parameters" });
|
|
208
|
+
return Redacted.make(url.href);
|
|
209
|
+
}, use),
|
|
210
|
+
codeGrant: Effect.fnUntraced(function* (input) {
|
|
211
|
+
yield* available;
|
|
212
|
+
const value = yield* configuration(CodeGrant, input);
|
|
213
|
+
const body = parameters(value);
|
|
214
|
+
body.set("grant_type", "authorization_code");
|
|
215
|
+
body.set("code", yield* reveal(value.code));
|
|
216
|
+
body.set("code_verifier", yield* reveal(value.pkceVerifier));
|
|
217
|
+
body.set("redirect_uri", value.redirectUri);
|
|
218
|
+
return yield* send(body);
|
|
219
|
+
}, use),
|
|
220
|
+
refreshGrant: Effect.fnUntraced(function* (input) {
|
|
221
|
+
yield* available;
|
|
222
|
+
const value = yield* configuration(RefreshGrant, input);
|
|
223
|
+
const body = parameters(value);
|
|
224
|
+
body.set("grant_type", "refresh_token");
|
|
225
|
+
body.set("refresh_token", yield* reveal(value.refreshToken));
|
|
226
|
+
if (value.scopes !== void 0) body.set("scope", value.scopes.join(" "));
|
|
227
|
+
return yield* send(body);
|
|
228
|
+
}, use),
|
|
229
|
+
fetchProfile: Effect.fnUntraced(function* (input) {
|
|
230
|
+
yield* available;
|
|
231
|
+
if (profile === void 0) return yield* ConfigurationError.make({ reason: "endpoint" });
|
|
232
|
+
const token = yield* reveal(yield* decode(Bearer, input));
|
|
233
|
+
const request$2 = HttpClientRequest.get(new URL(profile.url).href).pipe(HttpClientRequest.acceptJson, HttpClientRequest.setHeaders(profile.headers ?? {}), HttpClientRequest.setHeader("authorization", `Bearer ${token}`));
|
|
234
|
+
const response = yield* request(http, request$2, options);
|
|
235
|
+
if (response.status !== 200) return yield* Unavailable.make({});
|
|
236
|
+
return yield* reveal((yield* json(response)).body);
|
|
237
|
+
}, use),
|
|
238
|
+
revoke: Effect.fnUntraced(function* (input) {
|
|
239
|
+
yield* available;
|
|
240
|
+
if (metadata.revocation_endpoint === void 0) return yield* ConfigurationError.make({ reason: "endpoint" });
|
|
241
|
+
if (revokeSupported !== void 0 && !revokeSupported.includes(revocationAuthentication.method)) return yield* ConfigurationError.make({ reason: "authentication" });
|
|
242
|
+
const value = yield* configuration(Revocation, input);
|
|
243
|
+
const request$3 = yield* authenticated(new URL(metadata.revocation_endpoint).href, options.clientId, revocationAuthentication, new URLSearchParams({
|
|
244
|
+
token: yield* reveal(value.token),
|
|
245
|
+
token_type_hint: value.tokenTypeHint
|
|
246
|
+
}));
|
|
247
|
+
if ((yield* request(http, request$3, options)).status !== 200) return yield* Unavailable.make({});
|
|
248
|
+
}, use)
|
|
249
|
+
};
|
|
250
|
+
});
|
|
251
|
+
const Bearer = Schema.Redacted(text(16384).check(Schema.isPattern(/^[A-Za-z0-9._~+/-]+=*$/u)), { disallowJsonEncode: true });
|
|
252
|
+
const detachAuthentication = Effect.fnUntraced(function* (authentication) {
|
|
253
|
+
return authentication.method === "none" ? { ...authentication } : {
|
|
254
|
+
...authentication,
|
|
255
|
+
secret: Redacted.make(yield* reveal(authentication.secret))
|
|
256
|
+
};
|
|
257
|
+
});
|
|
258
|
+
//#endregion
|
|
259
|
+
export { Authentication, JsonObject, Metadata, TokenReceipt, TokenSet, make, tokens };
|
|
260
|
+
|
|
261
|
+
//# sourceMappingURL=OAuth.mjs.map
|