@nacre.work/core 0.5.2 → 0.5.5
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/dist/authserver.d.ts +100 -0
- package/dist/authserver.d.ts.map +1 -0
- package/dist/authserver.js +147 -0
- package/dist/authserver.js.map +1 -0
- package/dist/config.d.ts +27 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -0
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/migrations/0023_oauth_consent.sql +179 -0
- package/dist/migrations/0024_oauth_consents.sql +130 -0
- package/dist/oauth.d.ts +13 -10
- package/dist/oauth.d.ts.map +1 -1
- package/dist/oauth.js +13 -10
- package/dist/oauth.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
-- 0023 — an authorization server, and an owner for a service account.
|
|
2
|
+
--
|
|
3
|
+
-- Until now this product was a resource server and said so: it verifies tokens
|
|
4
|
+
-- and issues none through OAuth. That was honest and it left a hole an operator
|
|
5
|
+
-- fell into on day one — an MCP client's whole connect flow is discover, do the
|
|
6
|
+
-- OAuth dance, get a token, and the documented alternative was "make a service
|
|
7
|
+
-- account by hand, copy its key, paste it into a config file".
|
|
8
|
+
--
|
|
9
|
+
-- What is built here is deliberately not the usual answer. A consent screen
|
|
10
|
+
-- normally mints a token that acts **as the person who signed in**, and that is
|
|
11
|
+
-- the opposite of what this product sells: an agent is a principal of its own,
|
|
12
|
+
-- with its own grants, and "which documents may this agent read" is a different
|
|
13
|
+
-- question from "which documents may you read". So the code issued here is
|
|
14
|
+
-- exchanged for a token bound to a **service account**, and the consent screen
|
|
15
|
+
-- is where a person chooses or creates one.
|
|
16
|
+
--
|
|
17
|
+
-- Three tables and one column.
|
|
18
|
+
|
|
19
|
+
-- ─────────────────────────── who made an agent ───────────────────────────
|
|
20
|
+
|
|
21
|
+
-- A service account has never had an owner. `grants.created_by` records who
|
|
22
|
+
-- issued a grant; nothing recorded who created the principal it names, because
|
|
23
|
+
-- only an `org_admin` could and there were few of them.
|
|
24
|
+
--
|
|
25
|
+
-- Consent changes that: anyone who may issue a grant may now stand one up, and
|
|
26
|
+
-- without an owner an organization accumulates `laptop-agent`, `laptop-agent-2`
|
|
27
|
+
-- and nobody able to say whose is whose or to revoke their own.
|
|
28
|
+
--
|
|
29
|
+
-- Nullable, and that is not laziness: every account that already exists was
|
|
30
|
+
-- created before this column and by `init` or by an administrator through the
|
|
31
|
+
-- API, so there is no owner to recover. Inventing one — the first
|
|
32
|
+
-- administrator, say — would be a record that reads as fact and is a guess.
|
|
33
|
+
--
|
|
34
|
+
-- ON DELETE SET NULL rather than CASCADE. Deleting a person must not delete the
|
|
35
|
+
-- agents they set up; those are the organization's, and their access is decided
|
|
36
|
+
-- by grants that outlive whoever created them. (`users` are disabled rather
|
|
37
|
+
-- than deleted today, so this is a guard rather than a live path.)
|
|
38
|
+
ALTER TABLE service_accounts
|
|
39
|
+
ADD COLUMN created_by uuid REFERENCES users(id) ON DELETE SET NULL;
|
|
40
|
+
|
|
41
|
+
-- "My agents", which is the whole reason the column exists.
|
|
42
|
+
CREATE INDEX ON service_accounts (org_id, created_by) WHERE revoked_at IS NULL;
|
|
43
|
+
|
|
44
|
+
-- ────────────────────────── registered clients ──────────────────────────
|
|
45
|
+
|
|
46
|
+
-- A client registers **before anybody has signed in**, so this row carries no
|
|
47
|
+
-- tenant data and gets no `org_id` and no RLS. That is the honest shape rather
|
|
48
|
+
-- than a shortcut: a client_id is not access to anything. Authority is created
|
|
49
|
+
-- only by a consent a signed-in person completes, and that lives in the table
|
|
50
|
+
-- below, which is tenant data and is policed like every other.
|
|
51
|
+
--
|
|
52
|
+
-- Registration is open, which is what RFC 7591 is for and what an MCP client
|
|
53
|
+
-- expects to be able to do. The exposure is bounded by that same fact — a row
|
|
54
|
+
-- here permits nothing — plus the rate limiter, and a client is displayed to
|
|
55
|
+
-- the person consenting with its redirect URI beside its name, because the name
|
|
56
|
+
-- is self-asserted and the redirect URI is the thing that actually decides
|
|
57
|
+
-- where a code goes.
|
|
58
|
+
CREATE TABLE oauth_clients (
|
|
59
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
60
|
+
client_id text NOT NULL UNIQUE,
|
|
61
|
+
client_name text NOT NULL,
|
|
62
|
+
-- Exact strings, compared exactly. No wildcards and no prefix matching:
|
|
63
|
+
-- a redirect URI is where an authorization code is delivered, and every
|
|
64
|
+
-- relaxation of that comparison is a way to deliver it somewhere else.
|
|
65
|
+
redirect_uris text[] NOT NULL,
|
|
66
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
67
|
+
CHECK (cardinality(redirect_uris) > 0)
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
-- ─────────────────────── an authorization in flight ───────────────────────
|
|
71
|
+
|
|
72
|
+
-- Post-consent, and therefore tenant data: it names a service account, which
|
|
73
|
+
-- belongs to an organization.
|
|
74
|
+
--
|
|
75
|
+
-- The pre-consent half of the flow is deliberately **not** stored. An
|
|
76
|
+
-- authorization request that nobody has approved is a set of query parameters
|
|
77
|
+
-- the browser is already carrying; writing it down would add a table an
|
|
78
|
+
-- unauthenticated caller can fill. The parameters are carried through the
|
|
79
|
+
-- consent screen and validated when the consent is posted.
|
|
80
|
+
-- The composite key the reference below needs, and the reason it is composite:
|
|
81
|
+
-- a plain `REFERENCES service_accounts(id)` would let an authorization in one
|
|
82
|
+
-- organization name an agent in another, and the database would allow it. This
|
|
83
|
+
-- is the rule every cross-tenant foreign key here follows.
|
|
84
|
+
ALTER TABLE service_accounts ADD CONSTRAINT service_accounts_id_org_key UNIQUE (id, org_id);
|
|
85
|
+
|
|
86
|
+
CREATE TABLE oauth_authorizations (
|
|
87
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
88
|
+
org_id uuid NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
|
89
|
+
client_id text NOT NULL REFERENCES oauth_clients(client_id) ON DELETE CASCADE,
|
|
90
|
+
|
|
91
|
+
-- Who the token will act as. Composite so the database refuses an agent
|
|
92
|
+
-- from another organization, rather than the code that writes the insert.
|
|
93
|
+
service_account_id uuid NOT NULL,
|
|
94
|
+
|
|
95
|
+
-- Who approved it. An audit answer to "why does this agent have a token",
|
|
96
|
+
-- and the reason a person can be shown their own consents.
|
|
97
|
+
approved_by uuid NOT NULL REFERENCES users(id),
|
|
98
|
+
|
|
99
|
+
-- The code, hashed. Same rule as a service account key and for the same
|
|
100
|
+
-- reason: a readable authorization code in a backup is a token nobody
|
|
101
|
+
-- revoked. Unique so a hash collision is a constraint violation rather than
|
|
102
|
+
-- two authorizations answering to one code.
|
|
103
|
+
code_hash text NOT NULL UNIQUE,
|
|
104
|
+
|
|
105
|
+
-- PKCE. S256 only — `plain` is in the RFC and defeats the mechanism, so it
|
|
106
|
+
-- is refused at the endpoint and cannot be represented here.
|
|
107
|
+
code_challenge text NOT NULL,
|
|
108
|
+
|
|
109
|
+
-- Compared exactly against the one presented at the token endpoint. RFC
|
|
110
|
+
-- 6749 requires it and it is what stops a code being redeemed through a
|
|
111
|
+
-- different registered client's redirect.
|
|
112
|
+
redirect_uri text NOT NULL,
|
|
113
|
+
|
|
114
|
+
-- RFC 8707. The resource the token is bound to, so an audience-bound token
|
|
115
|
+
-- minted for this installation cannot be replayed at another.
|
|
116
|
+
resource text,
|
|
117
|
+
|
|
118
|
+
-- Short. A code is a bearer capability that exists only long enough to be
|
|
119
|
+
-- exchanged, and the exchange happens within a second of the redirect.
|
|
120
|
+
expires_at timestamptz NOT NULL,
|
|
121
|
+
-- Set on first exchange. A second attempt is refused **and** is a signal:
|
|
122
|
+
-- either the code leaked or the client is retrying, and neither should
|
|
123
|
+
-- produce a second token.
|
|
124
|
+
consumed_at timestamptz,
|
|
125
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
126
|
+
|
|
127
|
+
FOREIGN KEY (service_account_id, org_id) REFERENCES service_accounts (id, org_id) ON DELETE CASCADE
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
-- The lookup the token endpoint makes, and the sweep the worker makes.
|
|
131
|
+
CREATE INDEX ON oauth_authorizations (expires_at) WHERE consumed_at IS NULL;
|
|
132
|
+
CREATE INDEX ON oauth_authorizations (org_id, approved_by, created_at DESC);
|
|
133
|
+
|
|
134
|
+
ALTER TABLE oauth_authorizations ENABLE ROW LEVEL SECURITY;
|
|
135
|
+
ALTER TABLE oauth_authorizations FORCE ROW LEVEL SECURITY;
|
|
136
|
+
|
|
137
|
+
-- The same two-policy shape migration 0008 gave `service_accounts`, and for the
|
|
138
|
+
-- identical reason: an authorization code is a credential, and a credential is
|
|
139
|
+
-- what says which organization it belongs to. The token endpoint is handed a
|
|
140
|
+
-- code and nothing else, so it cannot scope the lookup before it has done it.
|
|
141
|
+
--
|
|
142
|
+
-- `CASE` and not `AND`: without the guard Postgres may evaluate the org-scoped
|
|
143
|
+
-- expression anyway and raise `unrecognized configuration parameter` before the
|
|
144
|
+
-- second policy is consulted. Left raising in every other case on purpose — a
|
|
145
|
+
-- query that forgot `withOrg` should fail loudly rather than quietly return
|
|
146
|
+
-- nothing.
|
|
147
|
+
CREATE POLICY org_isolation ON oauth_authorizations
|
|
148
|
+
USING (
|
|
149
|
+
CASE
|
|
150
|
+
WHEN current_setting('app.authenticating', true) = 'on' THEN false
|
|
151
|
+
ELSE org_id = current_setting('app.current_org')::uuid
|
|
152
|
+
END
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
-- **SELECT only**, which is the load-bearing half. The cross-tenant path reads
|
|
156
|
+
-- the code to learn which organization it belongs to and stops there; marking
|
|
157
|
+
-- it consumed happens afterwards inside `withOrg`, under that organization,
|
|
158
|
+
-- where the `consumed_at IS NULL` predicate in the UPDATE is what makes a
|
|
159
|
+
-- second exchange return no rows.
|
|
160
|
+
--
|
|
161
|
+
-- Splitting it that way is not a weakening. The lookup decides nothing — the
|
|
162
|
+
-- UPDATE does, atomically, and a replay loses the race against it. What the
|
|
163
|
+
-- split buys is that the one path in this system which spans tenants stays
|
|
164
|
+
-- unable to write, which is the property migration 0008 established and which
|
|
165
|
+
-- an UPDATE here would have quietly ended.
|
|
166
|
+
CREATE POLICY authenticating_lookup ON oauth_authorizations
|
|
167
|
+
FOR SELECT
|
|
168
|
+
USING (current_setting('app.authenticating', true) = 'on');
|
|
169
|
+
|
|
170
|
+
-- The application role reads and writes both, and can never rewrite a code that
|
|
171
|
+
-- has already been issued: `UPDATE` is granted because consumption sets one
|
|
172
|
+
-- column, and the endpoint sets only that one.
|
|
173
|
+
GRANT SELECT, INSERT ON oauth_clients TO nacre_app;
|
|
174
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON oauth_authorizations TO nacre_app;
|
|
175
|
+
|
|
176
|
+
-- The worker sweeps expired authorizations, the same way it prunes refresh
|
|
177
|
+
-- tokens: a code nobody exchanged is inert, and a table that only grows is a
|
|
178
|
+
-- table somebody eventually finds full.
|
|
179
|
+
GRANT SELECT, DELETE ON oauth_authorizations TO nacre_worker;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
-- 0024 — a connection you can see, and end.
|
|
2
|
+
--
|
|
3
|
+
-- 0023 built the flow and stopped one step short. It recorded an authorization
|
|
4
|
+
-- *code* — a thing that lives for ninety seconds and is consumed — and nothing
|
|
5
|
+
-- that outlives it. So after the exchange there was no record that an
|
|
6
|
+
-- application was connected at all, no screen could list one, and **the token
|
|
7
|
+
-- could not be taken back**: it is a JWT verified locally against a key, so it
|
|
8
|
+
-- is valid until it expires and nothing consults a table.
|
|
9
|
+
--
|
|
10
|
+
-- That is the gap this closes, and it closes it the way the product already
|
|
11
|
+
-- solves the same problem for a person's own session rather than by inventing a
|
|
12
|
+
-- second mechanism: a short access token, a long refresh token that is stored
|
|
13
|
+
-- and rotated, and revocation that kills the refresh token. The access token
|
|
14
|
+
-- then survives at most its own TTL, which is bounded and stated rather than
|
|
15
|
+
-- indefinite.
|
|
16
|
+
--
|
|
17
|
+
-- A denylist of live JWTs was the alternative and is rejected on the same
|
|
18
|
+
-- grounds the effective-principals cache was accepted: this one would put a
|
|
19
|
+
-- lookup on every single request to undo something that happens once, and a
|
|
20
|
+
-- verification that needs a database is no longer local verification.
|
|
21
|
+
|
|
22
|
+
-- ───────────────────────── the standing connection ─────────────────────────
|
|
23
|
+
|
|
24
|
+
CREATE TABLE oauth_consents (
|
|
25
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
26
|
+
org_id uuid NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
|
27
|
+
client_id text NOT NULL REFERENCES oauth_clients(client_id) ON DELETE CASCADE,
|
|
28
|
+
|
|
29
|
+
-- What the application acts as. Composite, so the database refuses an agent
|
|
30
|
+
-- from another organization rather than the code that writes the insert.
|
|
31
|
+
service_account_id uuid NOT NULL,
|
|
32
|
+
|
|
33
|
+
-- Who approved it, and therefore who is shown it as theirs. Not a
|
|
34
|
+
-- permission — an `org_admin` sees every connection in the organization,
|
|
35
|
+
-- because an agent is the organization's and somebody has to be able to end
|
|
36
|
+
-- one whose approver has left.
|
|
37
|
+
approved_by uuid NOT NULL REFERENCES users(id),
|
|
38
|
+
|
|
39
|
+
created_at timestamptz NOT NULL DEFAULT now(),
|
|
40
|
+
-- Moved when a refresh is exchanged, which is the only signal available:
|
|
41
|
+
-- an access token is verified locally and its use touches nothing. So this
|
|
42
|
+
-- is "last seen renewing" and the column name says so rather than implying
|
|
43
|
+
-- a precision it does not have.
|
|
44
|
+
last_refreshed_at timestamptz,
|
|
45
|
+
revoked_at timestamptz,
|
|
46
|
+
|
|
47
|
+
-- One connection per application per agent. Approving twice is the same
|
|
48
|
+
-- connection, not a second one — otherwise a screen fills with duplicates
|
|
49
|
+
-- and ending one leaves the others working.
|
|
50
|
+
UNIQUE (org_id, client_id, service_account_id),
|
|
51
|
+
FOREIGN KEY (service_account_id, org_id) REFERENCES service_accounts (id, org_id) ON DELETE CASCADE
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
CREATE INDEX ON oauth_consents (org_id, approved_by, created_at DESC);
|
|
55
|
+
|
|
56
|
+
ALTER TABLE oauth_consents ENABLE ROW LEVEL SECURITY;
|
|
57
|
+
ALTER TABLE oauth_consents FORCE ROW LEVEL SECURITY;
|
|
58
|
+
CREATE POLICY org_isolation ON oauth_consents
|
|
59
|
+
USING (org_id = current_setting('app.current_org')::uuid);
|
|
60
|
+
|
|
61
|
+
-- The code now points at the connection it belongs to, so the exchange knows
|
|
62
|
+
-- which one to hang a refresh token on. Nullable because 0023's rows predate
|
|
63
|
+
-- it, and they are all consumed or expired by now — a code lives ninety
|
|
64
|
+
-- seconds.
|
|
65
|
+
ALTER TABLE oauth_authorizations
|
|
66
|
+
ADD COLUMN consent_id uuid REFERENCES oauth_consents(id) ON DELETE CASCADE;
|
|
67
|
+
|
|
68
|
+
-- ────────────────────────── the refresh token ──────────────────────────
|
|
69
|
+
|
|
70
|
+
-- Deliberately **not** the `refresh_tokens` table sign-in uses. That one
|
|
71
|
+
-- references `users(id)` and is about a person's browser session; this is about
|
|
72
|
+
-- an application acting as an agent. Sharing a table would mean a nullable
|
|
73
|
+
-- `user_id`, a nullable `consent_id` and a CHECK to say exactly one is set —
|
|
74
|
+
-- three ways to describe one relationship, and a revocation query that has to
|
|
75
|
+
-- remember which shape it is looking at.
|
|
76
|
+
--
|
|
77
|
+
-- The rotation rule is the same and is the part worth copying: a used token
|
|
78
|
+
-- replays as an attack, because the legitimate holder has already exchanged it
|
|
79
|
+
-- and there is no way to tell which of the two holders is genuine. Replaying
|
|
80
|
+
-- one ends the whole family.
|
|
81
|
+
CREATE TABLE oauth_refresh_tokens (
|
|
82
|
+
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
83
|
+
org_id uuid NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
|
|
84
|
+
consent_id uuid NOT NULL REFERENCES oauth_consents(id) ON DELETE CASCADE,
|
|
85
|
+
-- Hashed. Same rule as a service account key and an authorization code: a
|
|
86
|
+
-- readable long-lived credential in a backup is one nobody revoked.
|
|
87
|
+
token_hash text NOT NULL UNIQUE,
|
|
88
|
+
-- Every token descended from one authorization shares this. Replaying a
|
|
89
|
+
-- spent token kills the family, not just the token.
|
|
90
|
+
family_id uuid NOT NULL,
|
|
91
|
+
expires_at timestamptz NOT NULL,
|
|
92
|
+
used_at timestamptz,
|
|
93
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
CREATE INDEX ON oauth_refresh_tokens (consent_id) WHERE used_at IS NULL;
|
|
97
|
+
CREATE INDEX ON oauth_refresh_tokens (expires_at);
|
|
98
|
+
|
|
99
|
+
ALTER TABLE oauth_refresh_tokens ENABLE ROW LEVEL SECURITY;
|
|
100
|
+
ALTER TABLE oauth_refresh_tokens FORCE ROW LEVEL SECURITY;
|
|
101
|
+
|
|
102
|
+
-- The same two-policy shape as `oauth_authorizations` and, before it,
|
|
103
|
+
-- `service_accounts`: a refresh token is a credential, and a credential is what
|
|
104
|
+
-- says which organization it belongs to. The token endpoint is handed one and
|
|
105
|
+
-- nothing else.
|
|
106
|
+
CREATE POLICY org_isolation ON oauth_refresh_tokens
|
|
107
|
+
USING (
|
|
108
|
+
CASE
|
|
109
|
+
WHEN current_setting('app.authenticating', true) = 'on' THEN false
|
|
110
|
+
ELSE org_id = current_setting('app.current_org')::uuid
|
|
111
|
+
END
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
-- SELECT only, and this is the property that must not erode: the one path in
|
|
115
|
+
-- this system that reads across tenants stays unable to write. The lookup finds
|
|
116
|
+
-- which organization a token belongs to and stops; rotating it happens inside
|
|
117
|
+
-- `withOrg`, where the `used_at IS NULL` predicate in the UPDATE is what makes
|
|
118
|
+
-- a replay lose the race.
|
|
119
|
+
CREATE POLICY authenticating_lookup ON oauth_refresh_tokens
|
|
120
|
+
FOR SELECT
|
|
121
|
+
USING (current_setting('app.authenticating', true) = 'on');
|
|
122
|
+
|
|
123
|
+
GRANT SELECT, INSERT, UPDATE ON oauth_consents TO nacre_app;
|
|
124
|
+
GRANT SELECT, INSERT, UPDATE, DELETE ON oauth_refresh_tokens TO nacre_app;
|
|
125
|
+
|
|
126
|
+
-- The worker prunes expired refresh tokens the same way it prunes the ones
|
|
127
|
+
-- sign-in issues: a table that only grows is one somebody eventually finds
|
|
128
|
+
-- full. Consents are not swept — a revoked connection is a record of something
|
|
129
|
+
-- that happened, and the screen showing it is the point.
|
|
130
|
+
GRANT SELECT, DELETE ON oauth_refresh_tokens TO nacre_worker;
|
package/dist/oauth.d.ts
CHANGED
|
@@ -15,17 +15,20 @@
|
|
|
15
15
|
* `/v1/auth/login` is email and password, and a service account key is a random
|
|
16
16
|
* string matched against a hash — neither is an OAuth grant.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* redirect further along: a client would fetch our metadata, learn we are our
|
|
23
|
-
* own issuer, ask us for a token endpoint, and find nothing again.
|
|
18
|
+
* `authorization_servers` names one, and which one is a deployment's choice:
|
|
19
|
+
* an identity provider if it has configured one, and otherwise this
|
|
20
|
+
* installation's own API, which since the consent flow landed *is* an
|
|
21
|
+
* authorization server.
|
|
24
22
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
23
|
+
* This field used to be absent by default, on the argument that pointing a
|
|
24
|
+
* client at a token endpoint that did not exist would be the same dead end the
|
|
25
|
+
* missing document was, one redirect further along. That argument was correct
|
|
26
|
+
* and it is not what changed — the endpoint changed. It exists now, and it
|
|
27
|
+
* mints a token bound to a **service account** rather than to the person who
|
|
28
|
+
* approved it, which is the part worth knowing before reading the flow.
|
|
29
|
+
*
|
|
30
|
+
* A deployment that wants neither sets `NACRE_OAUTH_AUTHORIZATION_SERVER` to
|
|
31
|
+
* its own provider; the field then names that and nothing here is consulted.
|
|
29
32
|
*/
|
|
30
33
|
export interface ProtectedResourceMetadata {
|
|
31
34
|
readonly resource: string;
|
package/dist/oauth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../oauth.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"oauth.d.ts","sourceRoot":"","sources":["../oauth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,qBAAqB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAClD,QAAQ,CAAC,wBAAwB,EAAE,SAAS,MAAM,EAAE,CAAA;IACpD,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAA;IACvC,QAAQ,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAA;CAC7C;AAED,gFAAgF;AAChF,eAAO,MAAM,uBAAuB,0CAA0C,CAAA;AAE9E;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,2BAA2B,CAAA;AAEjD,wBAAgB,yBAAyB,CAAC,KAAK,EAAE;IAC/C,+EAA+E;IAC/E,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,qEAAqE;IACrE,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAA;CACtC,GAAG,yBAAyB,CAoB5B"}
|
package/dist/oauth.js
CHANGED
|
@@ -15,17 +15,20 @@
|
|
|
15
15
|
* `/v1/auth/login` is email and password, and a service account key is a random
|
|
16
16
|
* string matched against a hash — neither is an OAuth grant.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* redirect further along: a client would fetch our metadata, learn we are our
|
|
23
|
-
* own issuer, ask us for a token endpoint, and find nothing again.
|
|
18
|
+
* `authorization_servers` names one, and which one is a deployment's choice:
|
|
19
|
+
* an identity provider if it has configured one, and otherwise this
|
|
20
|
+
* installation's own API, which since the consent flow landed *is* an
|
|
21
|
+
* authorization server.
|
|
24
22
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
23
|
+
* This field used to be absent by default, on the argument that pointing a
|
|
24
|
+
* client at a token endpoint that did not exist would be the same dead end the
|
|
25
|
+
* missing document was, one redirect further along. That argument was correct
|
|
26
|
+
* and it is not what changed — the endpoint changed. It exists now, and it
|
|
27
|
+
* mints a token bound to a **service account** rather than to the person who
|
|
28
|
+
* approved it, which is the part worth knowing before reading the flow.
|
|
29
|
+
*
|
|
30
|
+
* A deployment that wants neither sets `NACRE_OAUTH_AUTHORIZATION_SERVER` to
|
|
31
|
+
* its own provider; the field then names that and nothing here is consulted.
|
|
29
32
|
*/
|
|
30
33
|
/** The path RFC 9728 fixes, and the one the `WWW-Authenticate` header names. */
|
|
31
34
|
export const PROTECTED_RESOURCE_PATH = '/.well-known/oauth-protected-resource';
|
package/dist/oauth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth.js","sourceRoot":"","sources":["../oauth.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"oauth.js","sourceRoot":"","sources":["../oauth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAUH,gFAAgF;AAChF,MAAM,CAAC,MAAM,uBAAuB,GAAG,uCAAuC,CAAA;AAE9E;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,wBAAwB,CAAA;AAEjD,MAAM,UAAU,yBAAyB,CAAC,KAKzC;IACC,OAAO;QACL,0EAA0E;QAC1E,4EAA4E;QAC5E,0DAA0D;QAC1D,QAAQ,EAAE,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;QAChD,GAAG,CAAC,KAAK,CAAC,mBAAmB,KAAK,SAAS;YACzC,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,EAAE,qBAAqB,EAAE,CAAC,KAAK,CAAC,mBAAmB,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC;QAC/E,wEAAwE;QACxE,4EAA4E;QAC5E,sEAAsE;QACtE,wBAAwB,EAAE,CAAC,QAAQ,CAAC;QACpC,sBAAsB,EAAE,2DAA2D;QACnF,2EAA2E;QAC3E,yEAAyE;QACzE,qEAAqE;QACrE,oDAAoD;QACpD,gBAAgB,EAAE,EAAE;KACrB,CAAA;AACH,CAAC"}
|