@gjermundgaraba/clankerauth-sdk 0.4.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Gjermund Garaba
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,178 @@
1
+ # @gjermundgaraba/clankerauth-sdk
2
+
3
+ Effect-native [Clanker Auth](https://github.com/gjermundgaraba/clankerauth) verification and browser sessions. Verifies JWT access tokens and API keys and runs browser login with encrypted server-held credentials. Optional **effect-actions** integration provides request-scoped identity and OAuth discovery.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ vp add @gjermundgaraba/clankerauth-sdk
9
+ ```
10
+
11
+ Install Effect in the application:
12
+
13
+ ```sh
14
+ vp add effect@4.0.0-rc.116
15
+ ```
16
+
17
+ ## Verify tokens directly
18
+
19
+ Core consumers do not need effect-actions or `skipLibCheck`.
20
+
21
+ ```ts
22
+ import { Effect } from "effect";
23
+ import { FetchHttpClient } from "effect/unstable/http";
24
+ import { McpProtocol } from "effect/unstable/ai";
25
+ import { Verifier } from "@gjermundgaraba/clankerauth-sdk";
26
+
27
+ const makeVerifier = Verifier.make({
28
+ issuer: "https://auth.internal/api/auth",
29
+ resource: "https://notes.internal/api",
30
+ requiredScopes: ["notes:read"],
31
+ }).pipe(Effect.provide(FetchHttpClient.layer));
32
+ // Acquire once, then use verifier.verify(authorization) or verifier.verifyToken(token).
33
+ ```
34
+
35
+ ## Protect effect-actions routes
36
+
37
+ Install the optional peer dependency and import the integration subpath:
38
+
39
+ ```sh
40
+ vp add @gjermundgaraba/effect-actions@0.1.0
41
+ ```
42
+
43
+ Core and integration declarations both type-check without `skipLibCheck`.
44
+
45
+ Register a resource in the Clanker Auth dashboard. Its identifier is the exact audience URL, not just an origin. Acquire one resource capability per protected audience at application construction, not per request.
46
+
47
+ ```ts
48
+ import { Effect, Layer } from "effect";
49
+ import { FetchHttpClient } from "effect/unstable/http";
50
+ import { McpProtocol } from "effect/unstable/ai";
51
+ import { Resource } from "@gjermundgaraba/clankerauth-sdk/effect-actions";
52
+ import * as ActionMcp from "@gjermundgaraba/effect-actions/ActionMcp";
53
+
54
+ // Http and app are your effect-actions HTTP binding and implemented group.
55
+ const routes = Layer.unwrap(
56
+ Effect.gen(function* () {
57
+ const api = yield* Resource.make({
58
+ issuer: "https://auth.internal/api/auth",
59
+ resource: "https://notes.internal/api",
60
+ scopes: ["notes:read", "notes:write"],
61
+ requiredScopes: ["notes:read"],
62
+ });
63
+ const mcp = yield* Resource.make({
64
+ issuer: "https://auth.internal/api/auth",
65
+ resource: "https://notes.internal/mcp",
66
+ scopes: ["notes:read", "notes:write"],
67
+ requiredScopes: ["notes:read"],
68
+ });
69
+ return Layer.mergeAll(
70
+ api.discovery.layer,
71
+ mcp.discovery.layer,
72
+ Http.layer(app).pipe(Layer.provide(Resource.middleware(api).layer)),
73
+ ActionMcp.layerHttp(
74
+ { name: "notes", version: "1.0.0", path: "/mcp", protocols: [McpProtocol.v2026_07_28] },
75
+ app,
76
+ ).pipe(Layer.provide(Resource.middleware(mcp).layer)),
77
+ );
78
+ }),
79
+ ).pipe(Layer.provide(FetchHttpClient.layer));
80
+ ```
81
+
82
+ Serve this layer with Effect's `HttpRouter` and your Node server layer. If using `@effect/platform-node`, install the matching `@effect/platform-node@4.0.0-rc.116` package. Discovery is public; do not wrap it in authentication. Discovery cache policy belongs to the host. Authentication responses use `Cache-Control: no-store`.
83
+
84
+ The supplied `HttpClient` must not retry credential exchanges or follow redirects. The SDK overrides only FetchHttpClient's redirect policy to reject redirects, preserves other caller-provided fetch defaults, and never installs retry middleware.
85
+
86
+ An action reads identity from `CurrentPrincipal` and enforces its own permissions:
87
+
88
+ ```ts
89
+ import { Effect } from "effect";
90
+ import { Forbidden } from "@gjermundgaraba/clankerauth-sdk";
91
+ import { CurrentPrincipal } from "@gjermundgaraba/clankerauth-sdk/effect-actions";
92
+
93
+ const write = Effect.gen(function* () {
94
+ const principal = yield* CurrentPrincipal;
95
+ if (!principal.scopes.includes("notes:write")) {
96
+ return yield* new Forbidden({ message: "Write permission required" });
97
+ }
98
+ return principal.subject;
99
+ });
100
+ ```
101
+
102
+ Declare `Forbidden` in that action/group's error schemas. Middleware authenticates the request; it does not infer per-action policy or filter MCP tool discovery.
103
+
104
+ A principal contains `subject`, `scopes`, and `actor`: either `{ kind: "client", clientId }` or `{ kind: "key", keyId }`. The resource also exposes `verifier.verify(authorization)` and `verifier.verifyToken(token)` as Effects.
105
+
106
+ JWTs are checked against issuer, audience, EdDSA signature, token type, required claims, expiry and scopes. Sender-constrained tokens are rejected. JWKS lookups are cached for ten minutes. Unknown keys trigger one refresh and resolution retry, with a thirty-second cooldown to bound provider traffic. Failed miss-triggered refreshes also cool down and return 503; still-valid cached keys remain usable. Initial lookup failures are not retained. Removed keys can remain trusted until cache expiry. API keys are checked online on **every request**, so revocation is effective immediately. A resource that accepts only OAuth access tokens sets `apiKeys: false`; key-shaped bearers then fail as `Unauthorized` without contacting the issuer. Verification has a five-second deadline.
107
+
108
+ The bundled issuer does not configure automatic signing-key rotation. Immediate-use rotation is supported, but tokens signed by a new key can be rejected during the thirty-second cooldown after a successful lookup. Publish-before-use avoids that short window; it is not mandatory.
109
+
110
+ ## Browser sessions
111
+
112
+ Register a confidential client with the exact `callbackUrl` supplied to the session. It must be an absolute HTTP(S) URL without credentials or a fragment; query parameters are allowed. The session derives its browser origin and secure-cookie policy from this URL.
113
+
114
+ Provide a `SessionStore` layer implementing Effect operations:
115
+
116
+ - `get(id)` → `{ payload, expires } | undefined`
117
+ - `put(id, payload, expires)`
118
+ - `delete(id)`
119
+ - `sweep(now)`: remove rows expiring at or before `now`
120
+
121
+ Map expected database failures to `StoreError({ operation, cause })` at your persistence adapter. Do not put SQL, secrets, credentials or provider payloads in public errors. The store owns persistence, not encryption or session policy. **Run one process per store and acquire one shared browser-session capability per cookie/store configuration.**
122
+
123
+ ```ts
124
+ import { Effect, Layer, Redacted } from "effect";
125
+ import { BrowserSession } from "@gjermundgaraba/clankerauth-sdk";
126
+ import { BrowserActions, Resource } from "@gjermundgaraba/clankerauth-sdk/effect-actions";
127
+
128
+ // Inside application construction, with HttpClient and SessionStore provided:
129
+ const browser =
130
+ yield *
131
+ BrowserSession.make({
132
+ issuer: "https://auth.internal/api/auth",
133
+ resource: "https://notes.internal/api",
134
+ callbackUrl: "https://notes.internal/workspace/auth/callback",
135
+ clientId,
136
+ clientSecret: Redacted.make(clientSecret),
137
+ secret: Redacted.make(sessionSecret), // at least 32 characters; retain across restarts
138
+ scopes: ["notes:read", "notes:write"],
139
+ cookie: { name: "notes" },
140
+ verifyToken: api.verifier.verifyToken,
141
+ });
142
+
143
+ const browserRoutes = Layer.mergeAll(
144
+ BrowserActions.layer(browser),
145
+ Http.layer(app).pipe(Layer.provide(Resource.middleware(api, { browser }).layer)),
146
+ );
147
+ ```
148
+
149
+ This example's construction fragment belongs inside `Effect.gen`. Supply configuration and secrets at your composition root; the SDK does not read environment variables or start its own runtime.
150
+
151
+ `BrowserActions.layer` registers:
152
+
153
+ - `POST /auth/browser/login`: accepts `{ returnTo: "/path" }`, returns `{ url }`.
154
+ - `GET <callback pathname>`: consumes the one-time transaction and redirects (`/workspace/auth/callback` above).
155
+ - `POST /auth/browser/session`: returns the subject, scopes and issuer, or `Unauthorized`.
156
+ - `POST /auth/browser/logout`: ends the local session and attempts provider revocation.
157
+
158
+ Login, session and logout are effect-actions, with JSON bodies (`{}` for session and logout). Browser clients import the pure `Http` contract from `@gjermundgaraba/clankerauth-sdk/browser-api`. Only the OAuth callback is an ordinary HTTP endpoint. The callback uses the exact configured URL; action routes live under `/auth/browser`. Callback failures redirect to the origin root with an `auth_error` query parameter; a root-mounted callback returns the sanitized HTTP error to avoid a redirect loop. Login destinations must be same-origin paths other than the callback pathname. The host sets request-body limits, for example `HttpIncomingMessage.MaxBodySize` on Effect's Node server (Clanker Auth's own adapter uses 64 KiB). Responses are non-cacheable and use `Referrer-Policy: no-referrer`.
159
+
160
+ Tokens always remain in the server-side session store, never in browser JavaScript.
161
+
162
+ Pass `browser` only for HTTP routes the application's own pages call. A request with an Authorization header is always verified as a bearer token; without one, the session cookie authenticates it and Origin is checked on unsafe methods. **Never pass `browser` for MCP.**
163
+
164
+ The session capability does not accept incoming HTTP requests: `login(returnTo)`, `callback(url, transactionCookie)`, `session(sessionCookie)`, `accessToken(sessionCookie)`, and `logout(sessionCookie)` are Effects. HTTP handlers own cookie extraction and presentation.
165
+
166
+ Cookies are HttpOnly, SameSite=Lax and Secure on HTTPS. Login uses S256 PKCE, state, nonce, exact issuer validation and signed ID-token checks. Stored payloads remain AES-256-GCM sealed under the configured secret with hashed cookie identifiers.
167
+
168
+ Refreshes and logout serialize per session. A durable no-replay marker is written before refresh; interruptions, ambiguous outcomes and restarts cannot reuse that refresh token. Discovery failure before the marker remains retryable. Operational refresh failures return 503 and invalidate the session; subsequent access requires login. Local logout remains authoritative if provider revocation fails, with a sanitized Effect warning.
169
+
170
+ ## Errors and observability
171
+
172
+ Schema-tagged errors: `Unauthorized` (401), `Forbidden` (403), `RateLimited` (429), `ProviderUnavailable` and `StoreError` (503). Browser input errors use `InvalidRequest` (400). Invalid construction fails with `ConfigurationError`.
173
+
174
+ The SDK Resource adapter owns authentication error encoding and challenge headers; effect-actions supplies request-scoped identity and the no-store response policy. Browser actions use their declared error schemas; the callback handles redirects. Defects and interruption are not relabeled as authentication rejection. Named effects supply tracing boundaries; application logging/tracing layers remain caller-owned. No `onFailure` callbacks or hidden runtime.
175
+
176
+ Operational failures retain their underlying `cause` for application-side Effect error handling. `ProviderUnavailable`, `StoreError`, and `Unauthorized` keep this diagnostic field outside their public schemas; BrowserActions and effect-actions serialize only those schemas. Causes may contain sensitive transport or provider details: inspect selectively, never serialize them into responses or log them indiscriminately.
177
+
178
+ MIT licensed.
@@ -0,0 +1,5 @@
1
+ import { Layer } from "effect";
2
+ import { HttpRouter, HttpServerRequest } from "effect/unstable/http";
3
+ import { type BrowserSession } from "./browser.ts";
4
+ /** Login, session and logout actions under `/auth/browser`, plus the OAuth callback route. */
5
+ export declare const layer: (browser: BrowserSession) => Layer.Layer<never, never, import("effect/FileSystem").FileSystem | import("effect/unstable/http/Etag").Generator | import("effect/unstable/http/HttpPlatform").HttpPlatform | HttpRouter.HttpRouter | import("effect/Path").Path | HttpRouter.Request<"Requires", HttpServerRequest.HttpServerRequest>>;
@@ -0,0 +1,54 @@
1
+ import { Actions, Http } from "./browser-api.js";
2
+ import { Duration, Effect, Layer } from "effect";
3
+ import { HttpEffect, HttpRouter, HttpServerRequest, HttpServerResponse, } from "effect/unstable/http";
4
+ import { transactionLifetime } from "./browser.js";
5
+ import { authenticationErrors, encodeError, InvalidRequest } from "./errors.js";
6
+ const encode = encodeError([...authenticationErrors, InvalidRequest]);
7
+ /** Login, session and logout actions under `/auth/browser`, plus the OAuth callback route. */
8
+ export const layer = (browser) => {
9
+ const cookie = (suffix, value, age) => HttpServerResponse.setCookieUnsafe(`${browser.cookie.name}_${suffix}`, value, {
10
+ path: "/",
11
+ httpOnly: true,
12
+ sameSite: "lax",
13
+ secure: browser.cookie.secure,
14
+ maxAge: Duration.seconds(age),
15
+ });
16
+ const setCookie = (suffix, value, age) => HttpEffect.appendPreResponseHandler((_request, response) => Effect.succeed(cookie(suffix, value, age)(response)));
17
+ const sameOrigin = Effect.flatMap(HttpServerRequest.HttpServerRequest, (request) => browser.checkOrigin(request.headers.origin));
18
+ const sessionCookie = Effect.map(HttpServerRequest.HttpServerRequest, (request) => request.cookies[`${browser.cookie.name}_session`]);
19
+ const actions = Actions.implement({
20
+ login: ({ returnTo }) => Effect.gen(function* () {
21
+ yield* sameOrigin;
22
+ const result = yield* browser.login(returnTo);
23
+ yield* setCookie("login", result.transaction, transactionLifetime);
24
+ return { url: result.url };
25
+ }),
26
+ session: () => Effect.gen(function* () {
27
+ yield* sameOrigin;
28
+ return yield* browser.session(yield* sessionCookie);
29
+ }),
30
+ logout: () => Effect.gen(function* () {
31
+ yield* sameOrigin;
32
+ yield* browser.logout(yield* sessionCookie);
33
+ yield* setCookie("session", "", 0);
34
+ return {};
35
+ }),
36
+ });
37
+ const callback = Effect.gen(function* () {
38
+ const request = yield* HttpServerRequest.HttpServerRequest;
39
+ const url = yield* Effect.try({
40
+ try: () => new URL(request.originalUrl, browser.origin),
41
+ catch: () => new InvalidRequest({ message: "Invalid callback URL" }),
42
+ });
43
+ const result = yield* browser.callback(url, request.cookies[`${browser.cookie.name}_login`]);
44
+ return HttpServerResponse.redirect(result.location, { status: 302 }).pipe(cookie("session", result.session, browser.cookie.lifetime), cookie("login", "", 0));
45
+ }).pipe(Effect.catch((error) => encode(error).pipe(Effect.map((response) =>
46
+ // A root-mounted callback must report its error directly to avoid a redirect loop.
47
+ browser.callbackPath === "/"
48
+ ? response
49
+ : HttpServerResponse.redirect(`${browser.origin}/?auth_error=${response.status === 503 ? "unavailable" : "login_failed"}`, { status: 302 })), Effect.map(cookie("login", "", 0)))));
50
+ return Layer.mergeAll(Http.layer(actions), HttpRouter.add("GET", browser.callbackPath, callback)).pipe(Layer.provide(HttpRouter.middleware((effect) => HttpEffect.appendPreResponseHandler((_request, response) => Effect.succeed(HttpServerResponse.setHeaders(response, {
51
+ "cache-control": "no-store",
52
+ "referrer-policy": "no-referrer",
53
+ }))).pipe(Effect.andThen(effect))).layer));
54
+ };
@@ -0,0 +1,23 @@
1
+ import { Schema } from "effect";
2
+ import * as Action from "@gjermundgaraba/effect-actions/Action";
3
+ import * as ActionGroup from "@gjermundgaraba/effect-actions/ActionGroup";
4
+ import * as ActionHttp from "@gjermundgaraba/effect-actions/ActionHttp";
5
+ import { InvalidRequest } from "./errors.ts";
6
+ export declare const Actions: ActionGroup.Group<"browser", readonly [Action.Action<"login", Schema.Struct<{
7
+ readonly returnTo: Schema.String;
8
+ }>, Schema.Struct<{
9
+ readonly url: Schema.String;
10
+ }>, readonly [typeof import("./errors.ts").Unauthorized, typeof import("./errors.ts").Forbidden, typeof import("./errors.ts").RateLimited, typeof import("./errors.ts").ProviderUnavailable, typeof import("./errors.ts").StoreError, typeof InvalidRequest], true, false>, Action.Action<"session", Schema.$Record<Schema.String, Schema.Never>, Schema.Struct<{
11
+ readonly subject: Schema.String;
12
+ readonly scopes: Schema.$Array<Schema.String>;
13
+ readonly issuer: Schema.String;
14
+ }>, readonly [typeof import("./errors.ts").Unauthorized, typeof import("./errors.ts").Forbidden, typeof import("./errors.ts").RateLimited, typeof import("./errors.ts").ProviderUnavailable, typeof import("./errors.ts").StoreError, typeof InvalidRequest], true, false>, Action.Action<"logout", Schema.$Record<Schema.String, Schema.Never>, Schema.Struct<{}>, readonly [typeof import("./errors.ts").Unauthorized, typeof import("./errors.ts").Forbidden, typeof import("./errors.ts").RateLimited, typeof import("./errors.ts").ProviderUnavailable, typeof import("./errors.ts").StoreError, typeof InvalidRequest], true, false>], []>;
15
+ export declare const Http: ActionHttp.Http<readonly [ActionGroup.Group<"browser", readonly [Action.Action<"login", Schema.Struct<{
16
+ readonly returnTo: Schema.String;
17
+ }>, Schema.Struct<{
18
+ readonly url: Schema.String;
19
+ }>, readonly [typeof import("./errors.ts").Unauthorized, typeof import("./errors.ts").Forbidden, typeof import("./errors.ts").RateLimited, typeof import("./errors.ts").ProviderUnavailable, typeof import("./errors.ts").StoreError, typeof InvalidRequest], true, false>, Action.Action<"session", Schema.$Record<Schema.String, Schema.Never>, Schema.Struct<{
20
+ readonly subject: Schema.String;
21
+ readonly scopes: Schema.$Array<Schema.String>;
22
+ readonly issuer: Schema.String;
23
+ }>, readonly [typeof import("./errors.ts").Unauthorized, typeof import("./errors.ts").Forbidden, typeof import("./errors.ts").RateLimited, typeof import("./errors.ts").ProviderUnavailable, typeof import("./errors.ts").StoreError, typeof InvalidRequest], true, false>, Action.Action<"logout", Schema.$Record<Schema.String, Schema.Never>, Schema.Struct<{}>, readonly [typeof import("./errors.ts").Unauthorized, typeof import("./errors.ts").Forbidden, typeof import("./errors.ts").RateLimited, typeof import("./errors.ts").ProviderUnavailable, typeof import("./errors.ts").StoreError, typeof InvalidRequest], true, false>], []>]>;
@@ -0,0 +1,24 @@
1
+ import { Schema } from "effect";
2
+ import * as Action from "@gjermundgaraba/effect-actions/Action";
3
+ import * as ActionGroup from "@gjermundgaraba/effect-actions/ActionGroup";
4
+ import * as ActionHttp from "@gjermundgaraba/effect-actions/ActionHttp";
5
+ import { authenticationErrors, InvalidRequest } from "./errors.js";
6
+ export const Actions = ActionGroup.make({ name: "browser", errors: [...authenticationErrors, InvalidRequest] }, Action.make("login", {
7
+ description: "Start an OAuth authorization-code flow with PKCE.",
8
+ input: Schema.Struct({ returnTo: Schema.String }),
9
+ success: Schema.Struct({ url: Schema.String }),
10
+ mcp: false,
11
+ }), Action.make("session", {
12
+ description: "Read the current application session.",
13
+ success: Schema.Struct({
14
+ subject: Schema.String,
15
+ scopes: Schema.Array(Schema.String),
16
+ issuer: Schema.String,
17
+ }),
18
+ mcp: false,
19
+ }), Action.make("logout", {
20
+ description: "End this application session and revoke its refresh token.",
21
+ success: Schema.Struct({}),
22
+ mcp: false,
23
+ }));
24
+ export const Http = ActionHttp.make({ apiPath: "/auth" }, Actions);
@@ -0,0 +1,49 @@
1
+ import { Effect, Redacted } from "effect";
2
+ import { HttpClient } from "effect/unstable/http";
3
+ import { ConfigurationError, Forbidden, InvalidRequest, ProviderUnavailable, Unauthorized } from "./errors.ts";
4
+ import { SessionStore } from "./store.ts";
5
+ import type { Verifier } from "./verify.ts";
6
+ export interface Options {
7
+ readonly issuer: string;
8
+ readonly clientId: string;
9
+ readonly clientSecret: Redacted.Redacted<string>;
10
+ readonly callbackUrl: string;
11
+ readonly resource: string;
12
+ readonly scopes: readonly string[];
13
+ readonly secret: Redacted.Redacted<string>;
14
+ readonly cookie: {
15
+ readonly name: string;
16
+ readonly lifetime?: number;
17
+ };
18
+ readonly verifyToken: Verifier["verifyToken"];
19
+ }
20
+ export declare const transactionLifetime = 600;
21
+ /** Domain/session capability. Cookies and HTTP response mapping live in BrowserActions. */
22
+ export declare const make: (options: Options) => Effect.Effect<{
23
+ login: (returnTo: string) => Effect.Effect<{
24
+ url: string;
25
+ transaction: string;
26
+ }, InvalidRequest | ProviderUnavailable | import("./errors.ts").StoreError | Unauthorized, never>;
27
+ callback: (url: URL, raw: string | undefined) => Effect.Effect<{
28
+ session: string;
29
+ location: string;
30
+ }, import("./errors.ts").StoreError | import("./verify.ts").VerificationError, never>;
31
+ session: (raw: string | undefined) => Effect.Effect<{
32
+ subject: string;
33
+ scopes: readonly string[];
34
+ issuer: string;
35
+ }, import("./errors.ts").StoreError | import("./verify.ts").VerificationError, never>;
36
+ logout: (raw: string | undefined) => Effect.Effect<void, import("./errors.ts").StoreError | Unauthorized, never>;
37
+ accessToken: (raw: string | undefined) => Effect.Effect<string, import("./errors.ts").StoreError | import("./verify.ts").VerificationError, never>;
38
+ origin: string;
39
+ callbackPath: `/${string}`;
40
+ resource: string;
41
+ issuer: string;
42
+ cookie: {
43
+ name: string;
44
+ lifetime: number;
45
+ secure: boolean;
46
+ };
47
+ checkOrigin: (value: string | undefined) => Effect.Effect<void, never, never> | Effect.Effect<never, Forbidden, never>;
48
+ }, ConfigurationError, HttpClient.HttpClient | SessionStore>;
49
+ export type BrowserSession = Effect.Success<ReturnType<typeof make>>;