@absolutejs/auth 0.35.0 → 0.36.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/dist/cli/migrate.js +16 -1
- package/dist/cli/migrate.js.map +4 -4
- package/dist/index.d.ts +33 -2
- package/dist/index.js +423 -20
- package/dist/index.js.map +10 -9
- package/dist/oidc/config.d.ts +76 -2
- package/dist/oidc/inMemoryStores.d.ts +2 -1
- package/dist/oidc/mtls.d.ts +11 -0
- package/dist/oidc/postgresStores.d.ts +214 -1
- package/dist/oidc/routes.d.ts +30 -0
- package/dist/oidc/types.d.ts +23 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4492,6 +4492,7 @@ var RESERVED_ACCESS_CLAIMS = new Set([
|
|
|
4492
4492
|
var buildAccessClaims = ({
|
|
4493
4493
|
act,
|
|
4494
4494
|
audience,
|
|
4495
|
+
clientCertThumbprint,
|
|
4495
4496
|
clientId,
|
|
4496
4497
|
dpopJkt,
|
|
4497
4498
|
extraClaims,
|
|
@@ -4516,8 +4517,15 @@ var buildAccessClaims = ({
|
|
|
4516
4517
|
};
|
|
4517
4518
|
if (act !== undefined)
|
|
4518
4519
|
claims.act = act;
|
|
4519
|
-
if (dpopJkt !== undefined)
|
|
4520
|
-
|
|
4520
|
+
if (dpopJkt !== undefined || clientCertThumbprint !== undefined) {
|
|
4521
|
+
const cnf = {};
|
|
4522
|
+
if (dpopJkt !== undefined)
|
|
4523
|
+
cnf.jkt = dpopJkt;
|
|
4524
|
+
if (clientCertThumbprint !== undefined) {
|
|
4525
|
+
cnf["x5t#S256"] = clientCertThumbprint;
|
|
4526
|
+
}
|
|
4527
|
+
claims.cnf = cnf;
|
|
4528
|
+
}
|
|
4521
4529
|
return claims;
|
|
4522
4530
|
};
|
|
4523
4531
|
var exchangeToken = async ({
|
|
@@ -4567,6 +4575,7 @@ var exchangeToken = async ({
|
|
|
4567
4575
|
var issueTokenSet = async ({
|
|
4568
4576
|
acr,
|
|
4569
4577
|
claims,
|
|
4578
|
+
clientCertThumbprint,
|
|
4570
4579
|
clientId,
|
|
4571
4580
|
config,
|
|
4572
4581
|
dpopJkt,
|
|
@@ -4584,6 +4593,7 @@ var issueTokenSet = async ({
|
|
|
4584
4593
|
sub
|
|
4585
4594
|
});
|
|
4586
4595
|
const accessPayload = buildAccessClaims({
|
|
4596
|
+
clientCertThumbprint,
|
|
4587
4597
|
clientId,
|
|
4588
4598
|
dpopJkt,
|
|
4589
4599
|
extraClaims: { ...accessExtra, ...acr === undefined ? {} : { acr } },
|
|
@@ -4790,6 +4800,128 @@ var exchangeDeviceCode = async ({
|
|
|
4790
4800
|
});
|
|
4791
4801
|
return { ...tokenSet, ok: true };
|
|
4792
4802
|
};
|
|
4803
|
+
var AUTH_REQ_ID_BYTES = 32;
|
|
4804
|
+
var DEFAULT_BACKCHANNEL_TTL_MINUTES = 10;
|
|
4805
|
+
var DEFAULT_BACKCHANNEL_TTL_MS = MILLISECONDS_IN_A_MINUTE * DEFAULT_BACKCHANNEL_TTL_MINUTES;
|
|
4806
|
+
var DEFAULT_BACKCHANNEL_POLL_INTERVAL_SECONDS = 5;
|
|
4807
|
+
var CIBA_GRANT_TYPE = "urn:openid:params:grant-type:ciba";
|
|
4808
|
+
var issueBackchannelAuth = async ({
|
|
4809
|
+
clientId,
|
|
4810
|
+
config,
|
|
4811
|
+
loginHint,
|
|
4812
|
+
bindingMessage,
|
|
4813
|
+
now = Date.now(),
|
|
4814
|
+
requestedScopes
|
|
4815
|
+
}) => {
|
|
4816
|
+
if (!config.backchannelAuthStore || !config.resolveBackchannelUser) {
|
|
4817
|
+
return { error: "invalid_request", ok: false };
|
|
4818
|
+
}
|
|
4819
|
+
const client = await config.clientStore.findClient(clientId);
|
|
4820
|
+
if (!client)
|
|
4821
|
+
return { error: "invalid_client", ok: false };
|
|
4822
|
+
const resolved = await config.resolveBackchannelUser({
|
|
4823
|
+
client,
|
|
4824
|
+
loginHint
|
|
4825
|
+
});
|
|
4826
|
+
if (!resolved)
|
|
4827
|
+
return { error: "unknown_user_id", ok: false };
|
|
4828
|
+
const authReqId = generateSecureToken(AUTH_REQ_ID_BYTES);
|
|
4829
|
+
const ttl = config.backchannelAuthTtlMs ?? DEFAULT_BACKCHANNEL_TTL_MS;
|
|
4830
|
+
const interval = config.backchannelPollIntervalSeconds ?? DEFAULT_BACKCHANNEL_POLL_INTERVAL_SECONDS;
|
|
4831
|
+
await config.backchannelAuthStore.saveBackchannelAuth({
|
|
4832
|
+
authReqId,
|
|
4833
|
+
bindingMessage,
|
|
4834
|
+
clientId,
|
|
4835
|
+
createdAt: now,
|
|
4836
|
+
expiresAt: now + ttl,
|
|
4837
|
+
intervalSeconds: interval,
|
|
4838
|
+
scopes: requestedScopes,
|
|
4839
|
+
status: "pending",
|
|
4840
|
+
userSub: resolved.sub
|
|
4841
|
+
});
|
|
4842
|
+
await config.onBackchannelAuthRequest?.({
|
|
4843
|
+
authReqId,
|
|
4844
|
+
bindingMessage,
|
|
4845
|
+
clientId,
|
|
4846
|
+
scopes: requestedScopes,
|
|
4847
|
+
userSub: resolved.sub
|
|
4848
|
+
});
|
|
4849
|
+
return {
|
|
4850
|
+
auth_req_id: authReqId,
|
|
4851
|
+
expires_in: Math.floor(ttl / MS_PER_SECOND),
|
|
4852
|
+
interval,
|
|
4853
|
+
ok: true
|
|
4854
|
+
};
|
|
4855
|
+
};
|
|
4856
|
+
var decideBackchannel = async (config, authReqId, approval) => {
|
|
4857
|
+
if (!config.backchannelAuthStore) {
|
|
4858
|
+
return { error: "not_configured", ok: false };
|
|
4859
|
+
}
|
|
4860
|
+
const record = await config.backchannelAuthStore.findByAuthReqId(authReqId);
|
|
4861
|
+
if (!record)
|
|
4862
|
+
return { error: "invalid_auth_req_id", ok: false };
|
|
4863
|
+
if (record.expiresAt < Date.now()) {
|
|
4864
|
+
return { error: "expired_token", ok: false };
|
|
4865
|
+
}
|
|
4866
|
+
if (record.status !== "pending") {
|
|
4867
|
+
return { error: "already_decided", ok: false };
|
|
4868
|
+
}
|
|
4869
|
+
await config.backchannelAuthStore.updateStatus(authReqId, approval.status, approval.userSub ?? record.userSub);
|
|
4870
|
+
return { ok: true };
|
|
4871
|
+
};
|
|
4872
|
+
var approveBackchannelAuth = async ({
|
|
4873
|
+
authReqId,
|
|
4874
|
+
config,
|
|
4875
|
+
userSub
|
|
4876
|
+
}) => decideBackchannel(config, authReqId, {
|
|
4877
|
+
status: "approved",
|
|
4878
|
+
userSub
|
|
4879
|
+
});
|
|
4880
|
+
var denyBackchannelAuth = async ({
|
|
4881
|
+
authReqId,
|
|
4882
|
+
config
|
|
4883
|
+
}) => decideBackchannel(config, authReqId, { status: "denied" });
|
|
4884
|
+
var exchangeBackchannelAuth = async ({
|
|
4885
|
+
authReqId,
|
|
4886
|
+
clientCertThumbprint,
|
|
4887
|
+
clientId,
|
|
4888
|
+
config,
|
|
4889
|
+
dpopJkt,
|
|
4890
|
+
now = Date.now()
|
|
4891
|
+
}) => {
|
|
4892
|
+
if (!config.backchannelAuthStore) {
|
|
4893
|
+
return { error: "invalid_grant", ok: false };
|
|
4894
|
+
}
|
|
4895
|
+
const record = await config.backchannelAuthStore.findByAuthReqId(authReqId);
|
|
4896
|
+
if (!record || record.clientId !== clientId) {
|
|
4897
|
+
return { error: "invalid_grant", ok: false };
|
|
4898
|
+
}
|
|
4899
|
+
if (record.expiresAt < now) {
|
|
4900
|
+
await config.backchannelAuthStore.deleteByAuthReqId(authReqId);
|
|
4901
|
+
return { error: "expired_token", ok: false };
|
|
4902
|
+
}
|
|
4903
|
+
if (record.lastPolledAt !== undefined && now - record.lastPolledAt < record.intervalSeconds * MS_PER_SECOND) {
|
|
4904
|
+
return { error: "slow_down", ok: false };
|
|
4905
|
+
}
|
|
4906
|
+
await config.backchannelAuthStore.recordPoll(authReqId, now);
|
|
4907
|
+
if (record.status === "pending") {
|
|
4908
|
+
return { error: "authorization_pending", ok: false };
|
|
4909
|
+
}
|
|
4910
|
+
if (record.status === "denied" || record.userSub === undefined) {
|
|
4911
|
+
return { error: "access_denied", ok: false };
|
|
4912
|
+
}
|
|
4913
|
+
await config.backchannelAuthStore.deleteByAuthReqId(authReqId);
|
|
4914
|
+
const tokenSet = await issueTokenSet({
|
|
4915
|
+
clientCertThumbprint,
|
|
4916
|
+
clientId,
|
|
4917
|
+
config,
|
|
4918
|
+
dpopJkt,
|
|
4919
|
+
now,
|
|
4920
|
+
scopes: record.scopes,
|
|
4921
|
+
sub: record.userSub
|
|
4922
|
+
});
|
|
4923
|
+
return { ...tokenSet, ok: true };
|
|
4924
|
+
};
|
|
4793
4925
|
|
|
4794
4926
|
// src/oidc/clientAuth.ts
|
|
4795
4927
|
var CLIENT_ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
|
|
@@ -4900,6 +5032,55 @@ var verifyJwtSignedByClient = ({
|
|
|
4900
5032
|
client
|
|
4901
5033
|
}) => verifyJwtSignedByClientImpl(client, jwt);
|
|
4902
5034
|
|
|
5035
|
+
// src/oidc/mtls.ts
|
|
5036
|
+
var RFC9440_HEADER = "client-cert";
|
|
5037
|
+
var SF_BINARY_PREFIX = ":";
|
|
5038
|
+
var SF_BINARY_SUFFIX = ":";
|
|
5039
|
+
var base64Decode2 = (value) => Uint8Array.from(atob(value), (char) => char.charCodeAt(0));
|
|
5040
|
+
var base64UrlEncode2 = (bytes) => {
|
|
5041
|
+
const binary = Array.from(bytes, (byte) => String.fromCharCode(byte)).join("");
|
|
5042
|
+
return btoa(binary).replace(/\+/gu, "-").replace(/\//gu, "_").replace(/=+$/u, "");
|
|
5043
|
+
};
|
|
5044
|
+
var computeCertThumbprint = async (derBytes) => {
|
|
5045
|
+
const digest = await crypto.subtle.digest("SHA-256", derBytes);
|
|
5046
|
+
return base64UrlEncode2(new Uint8Array(digest));
|
|
5047
|
+
};
|
|
5048
|
+
var extractRfc9440ClientCert = (headers) => {
|
|
5049
|
+
const raw = headers.get(RFC9440_HEADER);
|
|
5050
|
+
if (raw === null)
|
|
5051
|
+
return;
|
|
5052
|
+
const trimmed = raw.trim();
|
|
5053
|
+
if (!trimmed.startsWith(SF_BINARY_PREFIX) || !trimmed.endsWith(SF_BINARY_SUFFIX) || trimmed.length <= 2) {
|
|
5054
|
+
return;
|
|
5055
|
+
}
|
|
5056
|
+
try {
|
|
5057
|
+
return base64Decode2(trimmed.slice(1, -1));
|
|
5058
|
+
} catch {
|
|
5059
|
+
return;
|
|
5060
|
+
}
|
|
5061
|
+
};
|
|
5062
|
+
var resolveClientCert = async ({
|
|
5063
|
+
extract,
|
|
5064
|
+
headers
|
|
5065
|
+
}) => {
|
|
5066
|
+
if (extract !== undefined)
|
|
5067
|
+
return extract(headers);
|
|
5068
|
+
return extractRfc9440ClientCert(headers);
|
|
5069
|
+
};
|
|
5070
|
+
var verifyCertificateBoundToken = async ({
|
|
5071
|
+
cnfThumbprint,
|
|
5072
|
+
extract,
|
|
5073
|
+
headers
|
|
5074
|
+
}) => {
|
|
5075
|
+
if (cnfThumbprint === undefined)
|
|
5076
|
+
return false;
|
|
5077
|
+
const cert = await resolveClientCert({ extract, headers });
|
|
5078
|
+
if (cert === undefined)
|
|
5079
|
+
return false;
|
|
5080
|
+
const presented = await computeCertThumbprint(cert);
|
|
5081
|
+
return presented === cnfThumbprint;
|
|
5082
|
+
};
|
|
5083
|
+
|
|
4903
5084
|
// src/oidc/dpop.ts
|
|
4904
5085
|
var DEFAULT_MAX_AGE_MS = 60000;
|
|
4905
5086
|
var SECONDS_TO_MS = 1000;
|
|
@@ -5517,6 +5698,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
5517
5698
|
const introspectRoute = `${oidcRoute}/introspect`;
|
|
5518
5699
|
const revokeRoute = `${oidcRoute}/revoke`;
|
|
5519
5700
|
const deviceAuthorizationRoute = `${oidcRoute}/device_authorization`;
|
|
5701
|
+
const backchannelAuthorizationRoute = `${oidcRoute}/bc-authorize`;
|
|
5520
5702
|
const deviceApproveRoute = `${oidcRoute}/device/decision`;
|
|
5521
5703
|
const endSessionRoute = `${oidcRoute}/end_session`;
|
|
5522
5704
|
const parRoute = `${oidcRoute}/par`;
|
|
@@ -5535,27 +5717,57 @@ var oidcProviderRoutes = (config) => {
|
|
|
5535
5717
|
const matches = await constantTimeEqual(await hashToken(clientSecret), client.hashedSecret);
|
|
5536
5718
|
return matches ? client : undefined;
|
|
5537
5719
|
};
|
|
5720
|
+
const tryMtlsAuth = async ({
|
|
5721
|
+
candidate,
|
|
5722
|
+
extract,
|
|
5723
|
+
requestHeaders
|
|
5724
|
+
}) => {
|
|
5725
|
+
const registered = candidate?.tlsCertificateBoundThumbprints ?? [];
|
|
5726
|
+
if (candidate === undefined || registered.length === 0)
|
|
5727
|
+
return;
|
|
5728
|
+
const cert = await resolveClientCert({
|
|
5729
|
+
extract,
|
|
5730
|
+
headers: requestHeaders
|
|
5731
|
+
});
|
|
5732
|
+
if (cert === undefined)
|
|
5733
|
+
return;
|
|
5734
|
+
const presented = await computeCertThumbprint(cert);
|
|
5735
|
+
if (!registered.includes(presented))
|
|
5736
|
+
return;
|
|
5737
|
+
return { client: candidate, clientCertThumbprint: presented };
|
|
5738
|
+
};
|
|
5538
5739
|
const authenticateTokenClient = async ({
|
|
5539
5740
|
basicClientId,
|
|
5540
5741
|
basicClientSecret,
|
|
5541
5742
|
bodyClientAssertion,
|
|
5542
5743
|
bodyClientAssertionType,
|
|
5543
5744
|
bodyClientId,
|
|
5544
|
-
bodyClientSecret
|
|
5745
|
+
bodyClientSecret,
|
|
5746
|
+
requestHeaders
|
|
5545
5747
|
}) => {
|
|
5546
5748
|
if (bodyClientAssertion !== undefined && bodyClientAssertionType === CLIENT_ASSERTION_TYPE) {
|
|
5547
|
-
|
|
5749
|
+
const client2 = await verifyClientAssertion({
|
|
5548
5750
|
assertion: bodyClientAssertion,
|
|
5549
5751
|
expectedAudience: tokenUrl,
|
|
5550
5752
|
jtiStore: config.clientAssertionJtiStore,
|
|
5551
5753
|
resolveClient: clientStore.findClient
|
|
5552
5754
|
});
|
|
5755
|
+
return client2 === undefined ? undefined : { client: client2, clientCertThumbprint: undefined };
|
|
5553
5756
|
}
|
|
5554
5757
|
const clientId = bodyClientId ?? basicClientId;
|
|
5555
|
-
const clientSecret = bodyClientSecret ?? basicClientSecret;
|
|
5556
5758
|
if (clientId === undefined)
|
|
5557
5759
|
return;
|
|
5558
|
-
|
|
5760
|
+
const candidate = await clientStore.findClient(clientId);
|
|
5761
|
+
const mtlsResult = await tryMtlsAuth({
|
|
5762
|
+
candidate,
|
|
5763
|
+
extract: config.extractTlsClientCert,
|
|
5764
|
+
requestHeaders
|
|
5765
|
+
});
|
|
5766
|
+
if (mtlsResult !== undefined)
|
|
5767
|
+
return mtlsResult;
|
|
5768
|
+
const clientSecret = bodyClientSecret ?? basicClientSecret;
|
|
5769
|
+
const client = await authenticateClient(clientId, clientSecret);
|
|
5770
|
+
return client === undefined ? undefined : { client, clientCertThumbprint: undefined };
|
|
5559
5771
|
};
|
|
5560
5772
|
const dpopNonceChallenge = async (proof) => {
|
|
5561
5773
|
if (proof === undefined || config.dpopNonce === undefined) {
|
|
@@ -5580,7 +5792,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
5580
5792
|
status: HTTP_UNAUTHORIZED2
|
|
5581
5793
|
});
|
|
5582
5794
|
};
|
|
5583
|
-
const grantAuthorizationCode = async (client, body, dpop) => {
|
|
5795
|
+
const grantAuthorizationCode = async (client, body, dpop, clientCertThumbprint) => {
|
|
5584
5796
|
const {
|
|
5585
5797
|
code,
|
|
5586
5798
|
code_verifier: codeVerifier,
|
|
@@ -5604,6 +5816,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
5604
5816
|
return tokenResponse(await issueTokenSet({
|
|
5605
5817
|
acr: record.acr,
|
|
5606
5818
|
claims: record.claims,
|
|
5819
|
+
clientCertThumbprint,
|
|
5607
5820
|
clientId: client.clientId,
|
|
5608
5821
|
config,
|
|
5609
5822
|
dpopJkt: dpopResult?.jkt,
|
|
@@ -5612,7 +5825,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
5612
5825
|
sub: record.userId
|
|
5613
5826
|
}));
|
|
5614
5827
|
};
|
|
5615
|
-
const grantRefreshToken = async (client, body, dpop) => {
|
|
5828
|
+
const grantRefreshToken = async (client, body, dpop, clientCertThumbprint) => {
|
|
5616
5829
|
const presented = body.refresh_token;
|
|
5617
5830
|
if (presented === undefined) {
|
|
5618
5831
|
return oauthError2(HTTP_BAD_REQUEST2, "invalid_request");
|
|
@@ -5634,6 +5847,7 @@ var oidcProviderRoutes = (config) => {
|
|
|
5634
5847
|
return tokenResponse(await issueTokenSet({
|
|
5635
5848
|
acr: record.acr,
|
|
5636
5849
|
claims: record.claims,
|
|
5850
|
+
clientCertThumbprint,
|
|
5637
5851
|
clientId: client.clientId,
|
|
5638
5852
|
config,
|
|
5639
5853
|
dpopJkt: record.dpopJkt,
|
|
@@ -5671,6 +5885,39 @@ var oidcProviderRoutes = (config) => {
|
|
|
5671
5885
|
token_type: dpopResult === undefined ? "Bearer" : "DPoP"
|
|
5672
5886
|
}, HTTP_OK2);
|
|
5673
5887
|
};
|
|
5888
|
+
const grantBackchannel = async (client, body, dpop, clientCertThumbprint) => {
|
|
5889
|
+
if (config.backchannelAuthStore === undefined) {
|
|
5890
|
+
return oauthError2(HTTP_BAD_REQUEST2, "unsupported_grant_type");
|
|
5891
|
+
}
|
|
5892
|
+
if (body.auth_req_id === undefined) {
|
|
5893
|
+
return oauthError2(HTTP_BAD_REQUEST2, "invalid_request");
|
|
5894
|
+
}
|
|
5895
|
+
const dpopResult = dpop === undefined ? undefined : await verifyDpopProof({
|
|
5896
|
+
htm: "POST",
|
|
5897
|
+
htu: tokenUrl,
|
|
5898
|
+
proof: dpop
|
|
5899
|
+
});
|
|
5900
|
+
if (dpop !== undefined && dpopResult === undefined) {
|
|
5901
|
+
return oauthError2(HTTP_BAD_REQUEST2, "invalid_dpop_proof");
|
|
5902
|
+
}
|
|
5903
|
+
const result = await exchangeBackchannelAuth({
|
|
5904
|
+
authReqId: body.auth_req_id,
|
|
5905
|
+
clientCertThumbprint,
|
|
5906
|
+
clientId: client.clientId,
|
|
5907
|
+
config,
|
|
5908
|
+
dpopJkt: dpopResult?.jkt
|
|
5909
|
+
});
|
|
5910
|
+
if (!result.ok)
|
|
5911
|
+
return oauthError2(HTTP_BAD_REQUEST2, result.error);
|
|
5912
|
+
return jsonResponse({
|
|
5913
|
+
access_token: result.access_token,
|
|
5914
|
+
expires_in: result.expires_in,
|
|
5915
|
+
id_token: result.id_token,
|
|
5916
|
+
refresh_token: result.refresh_token,
|
|
5917
|
+
scope: result.scope,
|
|
5918
|
+
token_type: dpopResult === undefined ? "Bearer" : "DPoP"
|
|
5919
|
+
}, HTTP_OK2);
|
|
5920
|
+
};
|
|
5674
5921
|
const grantDeviceCode = async (client, body, dpop) => {
|
|
5675
5922
|
if (config.deviceAuthorizationStore === undefined) {
|
|
5676
5923
|
return oauthError2(HTTP_BAD_REQUEST2, "unsupported_grant_type");
|
|
@@ -5711,6 +5958,9 @@ var oidcProviderRoutes = (config) => {
|
|
|
5711
5958
|
if (config.deviceAuthorizationStore) {
|
|
5712
5959
|
grantTypes.push("urn:ietf:params:oauth:grant-type:device_code");
|
|
5713
5960
|
}
|
|
5961
|
+
if (config.backchannelAuthStore) {
|
|
5962
|
+
grantTypes.push(CIBA_GRANT_TYPE);
|
|
5963
|
+
}
|
|
5714
5964
|
const discovery = {
|
|
5715
5965
|
authorization_endpoint: `${issuer}${authorizeRoute}`,
|
|
5716
5966
|
backchannel_logout_session_supported: false,
|
|
@@ -5729,12 +5979,14 @@ var oidcProviderRoutes = (config) => {
|
|
|
5729
5979
|
response_types_supported: ["code"],
|
|
5730
5980
|
revocation_endpoint: `${issuer}${revokeRoute}`,
|
|
5731
5981
|
subject_types_supported: ["public"],
|
|
5982
|
+
tls_client_certificate_bound_access_tokens: true,
|
|
5732
5983
|
token_endpoint: tokenUrl,
|
|
5733
5984
|
token_endpoint_auth_methods_supported: [
|
|
5734
5985
|
"client_secret_basic",
|
|
5735
5986
|
"client_secret_post",
|
|
5736
5987
|
"none",
|
|
5737
|
-
"private_key_jwt"
|
|
5988
|
+
"private_key_jwt",
|
|
5989
|
+
"self_signed_tls_client_auth"
|
|
5738
5990
|
],
|
|
5739
5991
|
token_endpoint_auth_signing_alg_values_supported: ["ES256"],
|
|
5740
5992
|
userinfo_endpoint: `${issuer}${userinfoRoute}`
|
|
@@ -5742,6 +5994,11 @@ var oidcProviderRoutes = (config) => {
|
|
|
5742
5994
|
if (config.deviceAuthorizationStore) {
|
|
5743
5995
|
discovery.device_authorization_endpoint = `${issuer}${deviceAuthorizationRoute}`;
|
|
5744
5996
|
}
|
|
5997
|
+
if (config.backchannelAuthStore) {
|
|
5998
|
+
discovery.backchannel_authentication_endpoint = `${issuer}${backchannelAuthorizationRoute}`;
|
|
5999
|
+
discovery.backchannel_token_delivery_modes_supported = ["poll"];
|
|
6000
|
+
discovery.backchannel_user_code_parameter_supported = false;
|
|
6001
|
+
}
|
|
5745
6002
|
if (config.clientRegistrationTokenStore !== undefined) {
|
|
5746
6003
|
discovery.registration_endpoint = registrationBaseUrl;
|
|
5747
6004
|
}
|
|
@@ -5936,27 +6193,29 @@ var oidcProviderRoutes = (config) => {
|
|
|
5936
6193
|
scope: t12.Optional(t12.String()),
|
|
5937
6194
|
state: t12.Optional(t12.String())
|
|
5938
6195
|
})
|
|
5939
|
-
}).post(tokenRoute, async ({ body, headers }) => {
|
|
6196
|
+
}).post(tokenRoute, async ({ body, headers, request }) => {
|
|
5940
6197
|
const basic = readBasicAuth2(headers.authorization);
|
|
5941
|
-
const
|
|
6198
|
+
const auth = await authenticateTokenClient({
|
|
5942
6199
|
basicClientId: basic.clientId,
|
|
5943
6200
|
basicClientSecret: basic.clientSecret,
|
|
5944
6201
|
bodyClientAssertion: body.client_assertion,
|
|
5945
6202
|
bodyClientAssertionType: body.client_assertion_type,
|
|
5946
6203
|
bodyClientId: body.client_id,
|
|
5947
|
-
bodyClientSecret: body.client_secret
|
|
6204
|
+
bodyClientSecret: body.client_secret,
|
|
6205
|
+
requestHeaders: request.headers
|
|
5948
6206
|
});
|
|
5949
|
-
if (
|
|
6207
|
+
if (auth === undefined) {
|
|
5950
6208
|
return oauthError2(HTTP_UNAUTHORIZED2, "invalid_client");
|
|
5951
6209
|
}
|
|
6210
|
+
const { client, clientCertThumbprint } = auth;
|
|
5952
6211
|
const nonceChallenge = await dpopNonceChallenge(headers.dpop);
|
|
5953
6212
|
if (nonceChallenge !== undefined)
|
|
5954
6213
|
return nonceChallenge;
|
|
5955
6214
|
if (body.grant_type === "authorization_code") {
|
|
5956
|
-
return grantAuthorizationCode(client, body, headers.dpop);
|
|
6215
|
+
return grantAuthorizationCode(client, body, headers.dpop, clientCertThumbprint);
|
|
5957
6216
|
}
|
|
5958
6217
|
if (body.grant_type === "refresh_token") {
|
|
5959
|
-
return grantRefreshToken(client, body, headers.dpop);
|
|
6218
|
+
return grantRefreshToken(client, body, headers.dpop, clientCertThumbprint);
|
|
5960
6219
|
}
|
|
5961
6220
|
if (body.grant_type === "urn:ietf:params:oauth:grant-type:token-exchange") {
|
|
5962
6221
|
return grantTokenExchange(client, body, headers.dpop);
|
|
@@ -5964,10 +6223,14 @@ var oidcProviderRoutes = (config) => {
|
|
|
5964
6223
|
if (body.grant_type === "urn:ietf:params:oauth:grant-type:device_code") {
|
|
5965
6224
|
return grantDeviceCode(client, body, headers.dpop);
|
|
5966
6225
|
}
|
|
6226
|
+
if (body.grant_type === CIBA_GRANT_TYPE) {
|
|
6227
|
+
return grantBackchannel(client, body, headers.dpop, clientCertThumbprint);
|
|
6228
|
+
}
|
|
5967
6229
|
return oauthError2(HTTP_BAD_REQUEST2, "unsupported_grant_type");
|
|
5968
6230
|
}, {
|
|
5969
6231
|
body: t12.Object({
|
|
5970
6232
|
audience: t12.Optional(t12.String()),
|
|
6233
|
+
auth_req_id: t12.Optional(t12.String()),
|
|
5971
6234
|
client_assertion: t12.Optional(t12.String()),
|
|
5972
6235
|
client_assertion_type: t12.Optional(t12.String()),
|
|
5973
6236
|
client_id: t12.Optional(t12.String()),
|
|
@@ -5983,22 +6246,24 @@ var oidcProviderRoutes = (config) => {
|
|
|
5983
6246
|
subject_token: t12.Optional(t12.String()),
|
|
5984
6247
|
subject_token_type: t12.Optional(t12.String())
|
|
5985
6248
|
})
|
|
5986
|
-
}).post(parRoute, async ({ body, headers }) => {
|
|
6249
|
+
}).post(parRoute, async ({ body, headers, request }) => {
|
|
5987
6250
|
if (config.pushedAuthorizationRequestStore === undefined) {
|
|
5988
6251
|
return oauthError2(HTTP_NOT_IMPLEMENTED, "unsupported_response_type");
|
|
5989
6252
|
}
|
|
5990
6253
|
const basic = readBasicAuth2(headers.authorization);
|
|
5991
|
-
const
|
|
6254
|
+
const auth = await authenticateTokenClient({
|
|
5992
6255
|
basicClientId: basic.clientId,
|
|
5993
6256
|
basicClientSecret: basic.clientSecret,
|
|
5994
6257
|
bodyClientAssertion: body.client_assertion,
|
|
5995
6258
|
bodyClientAssertionType: body.client_assertion_type,
|
|
5996
6259
|
bodyClientId: body.client_id,
|
|
5997
|
-
bodyClientSecret: body.client_secret
|
|
6260
|
+
bodyClientSecret: body.client_secret,
|
|
6261
|
+
requestHeaders: request.headers
|
|
5998
6262
|
});
|
|
5999
|
-
if (
|
|
6263
|
+
if (auth === undefined) {
|
|
6000
6264
|
return oauthError2(HTTP_UNAUTHORIZED2, "invalid_client");
|
|
6001
6265
|
}
|
|
6266
|
+
const { client } = auth;
|
|
6002
6267
|
const isAuthField = (key) => key === "client_assertion" || key === "client_assertion_type" || key === "client_secret";
|
|
6003
6268
|
const params = Object.fromEntries(Object.entries(body).filter((entry) => typeof entry[1] === "string" && !isAuthField(entry[0])));
|
|
6004
6269
|
const result = await pushAuthorizationRequest({
|
|
@@ -6082,6 +6347,50 @@ var oidcProviderRoutes = (config) => {
|
|
|
6082
6347
|
headers: t12.Object({
|
|
6083
6348
|
authorization: t12.Optional(t12.String())
|
|
6084
6349
|
})
|
|
6350
|
+
}).post(backchannelAuthorizationRoute, async ({ body, headers }) => {
|
|
6351
|
+
if (config.backchannelAuthStore === undefined) {
|
|
6352
|
+
return oauthError2(HTTP_BAD_REQUEST2, "unsupported_grant_type");
|
|
6353
|
+
}
|
|
6354
|
+
const basic = readBasicAuth2(headers.authorization);
|
|
6355
|
+
const clientId = body.client_id ?? basic.clientId;
|
|
6356
|
+
const clientSecret = body.client_secret ?? basic.clientSecret;
|
|
6357
|
+
if (clientId === undefined) {
|
|
6358
|
+
return oauthError2(HTTP_UNAUTHORIZED2, "invalid_client");
|
|
6359
|
+
}
|
|
6360
|
+
const client = await authenticateClient(clientId, clientSecret);
|
|
6361
|
+
if (client === undefined) {
|
|
6362
|
+
return oauthError2(HTTP_UNAUTHORIZED2, "invalid_client");
|
|
6363
|
+
}
|
|
6364
|
+
if (body.login_hint === undefined) {
|
|
6365
|
+
return oauthError2(HTTP_BAD_REQUEST2, "invalid_request");
|
|
6366
|
+
}
|
|
6367
|
+
const requested = body.scope === undefined || body.scope.length === 0 ? client.scopes : body.scope.split(" ").filter((entry) => client.scopes.includes(entry));
|
|
6368
|
+
const result = await issueBackchannelAuth({
|
|
6369
|
+
bindingMessage: body.binding_message,
|
|
6370
|
+
clientId: client.clientId,
|
|
6371
|
+
config,
|
|
6372
|
+
loginHint: body.login_hint,
|
|
6373
|
+
now: Date.now(),
|
|
6374
|
+
requestedScopes: requested
|
|
6375
|
+
});
|
|
6376
|
+
if (!result.ok)
|
|
6377
|
+
return oauthError2(HTTP_BAD_REQUEST2, result.error);
|
|
6378
|
+
return jsonResponse({
|
|
6379
|
+
auth_req_id: result.auth_req_id,
|
|
6380
|
+
expires_in: result.expires_in,
|
|
6381
|
+
interval: result.interval
|
|
6382
|
+
}, HTTP_OK2);
|
|
6383
|
+
}, {
|
|
6384
|
+
body: t12.Object({
|
|
6385
|
+
binding_message: t12.Optional(t12.String()),
|
|
6386
|
+
client_id: t12.Optional(t12.String()),
|
|
6387
|
+
client_secret: t12.Optional(t12.String()),
|
|
6388
|
+
login_hint: t12.Optional(t12.String()),
|
|
6389
|
+
scope: t12.Optional(t12.String())
|
|
6390
|
+
}),
|
|
6391
|
+
headers: t12.Object({
|
|
6392
|
+
authorization: t12.Optional(t12.String())
|
|
6393
|
+
})
|
|
6085
6394
|
}).post(deviceAuthorizationRoute, async ({ body, headers }) => {
|
|
6086
6395
|
if (config.deviceAuthorizationStore === undefined) {
|
|
6087
6396
|
return oauthError2(HTTP_BAD_REQUEST2, "unsupported_grant_type");
|
|
@@ -21542,6 +21851,30 @@ var createInMemoryAuthorizationCodeStore = () => {
|
|
|
21542
21851
|
}
|
|
21543
21852
|
};
|
|
21544
21853
|
};
|
|
21854
|
+
var createInMemoryBackchannelAuthStore = () => {
|
|
21855
|
+
const byAuthReqId = new Map;
|
|
21856
|
+
return {
|
|
21857
|
+
deleteByAuthReqId: async (authReqId) => {
|
|
21858
|
+
byAuthReqId.delete(authReqId);
|
|
21859
|
+
},
|
|
21860
|
+
findByAuthReqId: async (authReqId) => byAuthReqId.get(authReqId),
|
|
21861
|
+
recordPoll: async (authReqId, polledAt) => {
|
|
21862
|
+
const record = byAuthReqId.get(authReqId);
|
|
21863
|
+
if (!record)
|
|
21864
|
+
return;
|
|
21865
|
+
byAuthReqId.set(authReqId, { ...record, lastPolledAt: polledAt });
|
|
21866
|
+
},
|
|
21867
|
+
saveBackchannelAuth: async (request) => {
|
|
21868
|
+
byAuthReqId.set(request.authReqId, { ...request });
|
|
21869
|
+
},
|
|
21870
|
+
updateStatus: async (authReqId, status, userSub) => {
|
|
21871
|
+
const record = byAuthReqId.get(authReqId);
|
|
21872
|
+
if (!record)
|
|
21873
|
+
return;
|
|
21874
|
+
byAuthReqId.set(authReqId, { ...record, status, userSub });
|
|
21875
|
+
}
|
|
21876
|
+
};
|
|
21877
|
+
};
|
|
21545
21878
|
var createInMemoryClientAssertionJtiStore = () => {
|
|
21546
21879
|
const seen = new Map;
|
|
21547
21880
|
return {
|
|
@@ -21689,6 +22022,20 @@ var createInMemoryPushedAuthorizationRequestStore = () => {
|
|
|
21689
22022
|
var URL_LENGTH = 2048;
|
|
21690
22023
|
var DEFAULT_LIST_LIMIT2 = 100;
|
|
21691
22024
|
var ID_LENGTH7 = 255;
|
|
22025
|
+
var oauthBackchannelAuthRequestsTable = pgTable("auth_oauth_backchannel_auth_requests", {
|
|
22026
|
+
auth_req_id: varchar("auth_req_id", { length: ID_LENGTH7 }).primaryKey(),
|
|
22027
|
+
binding_message: text("binding_message"),
|
|
22028
|
+
client_id: varchar("client_id", { length: ID_LENGTH7 }).notNull(),
|
|
22029
|
+
created_at_ms: bigint("created_at_ms", { mode: "number" }).notNull(),
|
|
22030
|
+
expires_at_ms: bigint("expires_at_ms", { mode: "number" }).notNull(),
|
|
22031
|
+
interval_seconds: bigint("interval_seconds", {
|
|
22032
|
+
mode: "number"
|
|
22033
|
+
}).notNull(),
|
|
22034
|
+
last_polled_at_ms: bigint("last_polled_at_ms", { mode: "number" }),
|
|
22035
|
+
scopes: text("scopes").array().notNull(),
|
|
22036
|
+
status: varchar("status", { length: 16 }).notNull(),
|
|
22037
|
+
user_sub: varchar("user_sub", { length: ID_LENGTH7 })
|
|
22038
|
+
});
|
|
21692
22039
|
var oauthClientAssertionJtisTable = pgTable("auth_oauth_client_assertion_jtis", {
|
|
21693
22040
|
client_id: varchar("client_id", { length: ID_LENGTH7 }).notNull(),
|
|
21694
22041
|
composite_key: varchar("composite_key", {
|
|
@@ -22054,6 +22401,48 @@ var createPostgresPushedAuthorizationRequestStore = (db) => ({
|
|
|
22054
22401
|
});
|
|
22055
22402
|
}
|
|
22056
22403
|
});
|
|
22404
|
+
var toBackchannelAuth = (row) => ({
|
|
22405
|
+
authReqId: row.auth_req_id,
|
|
22406
|
+
bindingMessage: row.binding_message ?? undefined,
|
|
22407
|
+
clientId: row.client_id,
|
|
22408
|
+
createdAt: row.created_at_ms,
|
|
22409
|
+
expiresAt: row.expires_at_ms,
|
|
22410
|
+
intervalSeconds: row.interval_seconds,
|
|
22411
|
+
lastPolledAt: row.last_polled_at_ms ?? undefined,
|
|
22412
|
+
scopes: row.scopes,
|
|
22413
|
+
status: row.status,
|
|
22414
|
+
userSub: row.user_sub ?? undefined
|
|
22415
|
+
});
|
|
22416
|
+
var createNeonBackchannelAuthStore = (databaseUrl) => createPostgresBackchannelAuthStore(createNeonDatabase(databaseUrl));
|
|
22417
|
+
var createPostgresBackchannelAuthStore = (db) => ({
|
|
22418
|
+
deleteByAuthReqId: async (authReqId) => {
|
|
22419
|
+
await db.delete(oauthBackchannelAuthRequestsTable).where(eq(oauthBackchannelAuthRequestsTable.auth_req_id, authReqId));
|
|
22420
|
+
},
|
|
22421
|
+
findByAuthReqId: async (authReqId) => {
|
|
22422
|
+
const [row] = await db.select().from(oauthBackchannelAuthRequestsTable).where(eq(oauthBackchannelAuthRequestsTable.auth_req_id, authReqId)).limit(1);
|
|
22423
|
+
return row ? toBackchannelAuth(row) : undefined;
|
|
22424
|
+
},
|
|
22425
|
+
recordPoll: async (authReqId, polledAt) => {
|
|
22426
|
+
await db.update(oauthBackchannelAuthRequestsTable).set({ last_polled_at_ms: polledAt }).where(eq(oauthBackchannelAuthRequestsTable.auth_req_id, authReqId));
|
|
22427
|
+
},
|
|
22428
|
+
saveBackchannelAuth: async (request) => {
|
|
22429
|
+
await db.insert(oauthBackchannelAuthRequestsTable).values({
|
|
22430
|
+
auth_req_id: request.authReqId,
|
|
22431
|
+
binding_message: request.bindingMessage ?? null,
|
|
22432
|
+
client_id: request.clientId,
|
|
22433
|
+
created_at_ms: request.createdAt,
|
|
22434
|
+
expires_at_ms: request.expiresAt,
|
|
22435
|
+
interval_seconds: request.intervalSeconds,
|
|
22436
|
+
last_polled_at_ms: request.lastPolledAt ?? null,
|
|
22437
|
+
scopes: request.scopes,
|
|
22438
|
+
status: request.status,
|
|
22439
|
+
user_sub: request.userSub ?? null
|
|
22440
|
+
});
|
|
22441
|
+
},
|
|
22442
|
+
updateStatus: async (authReqId, status, userSub) => {
|
|
22443
|
+
await db.update(oauthBackchannelAuthRequestsTable).set({ status, user_sub: userSub ?? null }).where(eq(oauthBackchannelAuthRequestsTable.auth_req_id, authReqId));
|
|
22444
|
+
}
|
|
22445
|
+
});
|
|
22057
22446
|
// src/adaptive/config.ts
|
|
22058
22447
|
var DEFAULT_HISTORY_LIMIT = 50;
|
|
22059
22448
|
var DEFAULT_MAX_TRAVEL_KMH = 900;
|
|
@@ -23316,6 +23705,7 @@ var blockMigrations = {
|
|
|
23316
23705
|
lockout: initMigration("lockout", [lockoutsTable]),
|
|
23317
23706
|
mfa: initMigration("mfa", [mfaEnrollmentsTable]),
|
|
23318
23707
|
oidc: initMigration("oidc", [
|
|
23708
|
+
oauthBackchannelAuthRequestsTable,
|
|
23319
23709
|
oauthClientAssertionJtisTable,
|
|
23320
23710
|
oauthClientRegistrationTokensTable,
|
|
23321
23711
|
oauthClientsTable,
|
|
@@ -23950,6 +24340,7 @@ export {
|
|
|
23950
24340
|
verifyDpopNonce,
|
|
23951
24341
|
verifyCognitoSha256,
|
|
23952
24342
|
verifyClientAssertion,
|
|
24343
|
+
verifyCertificateBoundToken,
|
|
23953
24344
|
verifyAuth0Pbkdf2,
|
|
23954
24345
|
verifyAuditChain,
|
|
23955
24346
|
verifyApiKey,
|
|
@@ -23998,6 +24389,7 @@ export {
|
|
|
23998
24389
|
resolveOAuthAuthorization,
|
|
23999
24390
|
resolveCookieSecure,
|
|
24000
24391
|
resolveClientProviderEntry,
|
|
24392
|
+
resolveClientCert,
|
|
24001
24393
|
resolveAuthHtmxRenderers,
|
|
24002
24394
|
resolveApiPrincipal,
|
|
24003
24395
|
removeFromSessionRing,
|
|
@@ -24034,6 +24426,7 @@ export {
|
|
|
24034
24426
|
oauthClientsTable,
|
|
24035
24427
|
oauthClientRegistrationTokensTable,
|
|
24036
24428
|
oauthClientAssertionJtisTable,
|
|
24429
|
+
oauthBackchannelAuthRequestsTable,
|
|
24037
24430
|
mintLogoutToken,
|
|
24038
24431
|
mintDpopNonce,
|
|
24039
24432
|
mfaTotpRoutes,
|
|
@@ -24052,6 +24445,7 @@ export {
|
|
|
24052
24445
|
jwkThumbprint,
|
|
24053
24446
|
issueTokenSet,
|
|
24054
24447
|
issueDeviceAuthorization,
|
|
24448
|
+
issueBackchannelAuth,
|
|
24055
24449
|
isValidUser,
|
|
24056
24450
|
isValidProviderOption,
|
|
24057
24451
|
isUserSessionId,
|
|
@@ -24092,17 +24486,20 @@ export {
|
|
|
24092
24486
|
fingerprintDevice,
|
|
24093
24487
|
fetchUserInfo,
|
|
24094
24488
|
fanOutBackchannelLogout,
|
|
24489
|
+
extractRfc9440ClientCert,
|
|
24095
24490
|
extractPropFromIdentity,
|
|
24096
24491
|
extractDpopNonceClaim,
|
|
24097
24492
|
exportAuditCsv,
|
|
24098
24493
|
exchangeToken,
|
|
24099
24494
|
exchangeDeviceCode,
|
|
24100
24495
|
exchangeClientCredentials,
|
|
24496
|
+
exchangeBackchannelAuth,
|
|
24101
24497
|
evaluatePassword,
|
|
24102
24498
|
endImpersonation,
|
|
24103
24499
|
encryptTotpSecret,
|
|
24104
24500
|
encryptSecret,
|
|
24105
24501
|
denyDeviceAuthorization,
|
|
24502
|
+
denyBackchannelAuth,
|
|
24106
24503
|
deleteWarrant,
|
|
24107
24504
|
deleteRegisteredClient,
|
|
24108
24505
|
defineProvidersConfiguration,
|
|
@@ -24157,6 +24554,7 @@ export {
|
|
|
24157
24554
|
createPostgresCredentialStore,
|
|
24158
24555
|
createPostgresClientRegistrationTokenStore,
|
|
24159
24556
|
createPostgresClientAssertionJtiStore,
|
|
24557
|
+
createPostgresBackchannelAuthStore,
|
|
24160
24558
|
createPostgresAuthorizationCodeStore,
|
|
24161
24559
|
createPostgresAuditSink,
|
|
24162
24560
|
createPostgresApiKeyStore,
|
|
@@ -24191,6 +24589,7 @@ export {
|
|
|
24191
24589
|
createNeonCredentialStore,
|
|
24192
24590
|
createNeonClientRegistrationTokenStore,
|
|
24193
24591
|
createNeonClientAssertionJtiStore,
|
|
24592
|
+
createNeonBackchannelAuthStore,
|
|
24194
24593
|
createNeonAuthorizationCodeStore,
|
|
24195
24594
|
createNeonAuthSessionStore,
|
|
24196
24595
|
createNeonAuditSink,
|
|
@@ -24227,6 +24626,7 @@ export {
|
|
|
24227
24626
|
createInMemoryClientRegistrationTokenStore,
|
|
24228
24627
|
createInMemoryClientAssertionJtiStore,
|
|
24229
24628
|
createInMemoryCheckCache,
|
|
24629
|
+
createInMemoryBackchannelAuthStore,
|
|
24230
24630
|
createInMemoryAuthorizationCodeStore,
|
|
24231
24631
|
createInMemoryAuthSessionStore,
|
|
24232
24632
|
createInMemoryAuditSink,
|
|
@@ -24246,6 +24646,7 @@ export {
|
|
|
24246
24646
|
consumePushedRequest,
|
|
24247
24647
|
consumeBackupCode,
|
|
24248
24648
|
constantTimeEqual,
|
|
24649
|
+
computeCertThumbprint,
|
|
24249
24650
|
complianceRoutes,
|
|
24250
24651
|
check,
|
|
24251
24652
|
buildClientProviders,
|
|
@@ -24260,6 +24661,7 @@ export {
|
|
|
24260
24661
|
assessRisk,
|
|
24261
24662
|
assessAbuse,
|
|
24262
24663
|
approveDeviceAuthorization,
|
|
24664
|
+
approveBackchannelAuth,
|
|
24263
24665
|
apiKeysTable,
|
|
24264
24666
|
apiKeysRoutes,
|
|
24265
24667
|
apiClientsTable,
|
|
@@ -24297,8 +24699,9 @@ export {
|
|
|
24297
24699
|
DEFAULT_CREDENTIAL_SESSION_TTL_MS,
|
|
24298
24700
|
DEFAULT_BACKUP_CODE_COUNT,
|
|
24299
24701
|
CLIENT_ASSERTION_TYPE,
|
|
24702
|
+
CIBA_GRANT_TYPE,
|
|
24300
24703
|
AuthIdentityConflictError
|
|
24301
24704
|
};
|
|
24302
24705
|
|
|
24303
|
-
//# debugId=
|
|
24706
|
+
//# debugId=1069DAAF739CAF9F64756E2164756E21
|
|
24304
24707
|
//# sourceMappingURL=index.js.map
|