-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
452 additions
and
331 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
import express from "express"; | ||
import fs from "fs"; | ||
import { v4 as uuidv4 } from "uuid"; | ||
import { generateNonce, buildVpRequestJwt } from "../utils/cryptoUtils.js"; | ||
|
||
import { getAuthCodeSessions } from "../services/cacheService.js"; | ||
import qr from "qr-image"; | ||
import imageDataURI from "image-data-uri"; | ||
import { streamToBuffer } from "@jorgeferrero/stream-to-buffer"; | ||
|
||
const codeFlowRouter = express.Router(); | ||
|
||
const serverURL = process.env.SERVER_URL || "http://localhost:3000"; | ||
|
||
const privateKey = fs.readFileSync("./private-key.pem", "utf-8"); | ||
const publicKeyPem = fs.readFileSync("./public-key.pem", "utf-8"); | ||
|
||
// auth code flow | ||
codeFlowRouter.get(["/offer-code"], async (req, res) => { | ||
const uuid = req.query.sessionId ? req.query.sessionId : uuidv4(); | ||
const codeSessions = getAuthCodeSessions(); | ||
codeSessions.sessions.push(uuid); | ||
codeSessions.results.push({ sessionId: uuid, status: "pending" }); | ||
// console.log("active sessions"); | ||
// console.log(issuanceResults); | ||
let credentialOffer = `openid-credential-offer://?credential_offer_uri=${serverURL}/credential-offer-code/${uuid}`; | ||
|
||
let code = qr.image(credentialOffer, { | ||
type: "png", | ||
ec_level: "H", | ||
size: 10, | ||
margin: 10, | ||
}); | ||
let mediaType = "PNG"; | ||
let encodedQR = imageDataURI.encode(await streamToBuffer(code), mediaType); | ||
res.json({ | ||
qr: encodedQR, | ||
deepLink: credentialOffer, | ||
sessionId: uuid, | ||
}); | ||
}); | ||
|
||
// auth code-flow request | ||
codeFlowRouter.get(["/credential-offer-code/:id"], (req, res) => { | ||
res.json({ | ||
credential_issuer: serverURL, | ||
credentials: ["VerifiablePortableDocumentA2"], | ||
grants: { | ||
authorization_code: { | ||
issuer_state: req.params.id, | ||
}, | ||
}, | ||
}); | ||
}); | ||
|
||
codeFlowRouter.get("/authorize", async (req, res) => { | ||
const responseType = req.query.response_type; | ||
const scope = req.query.scope; | ||
const issuerState = decodeURIComponent(req.query.issuer_state); // This can be associated with the ITB session | ||
const state = req.query.state; | ||
const clientId = decodeURIComponent(req.query.client_id); //DID of the holder requesting the credential | ||
const authorizationDetails = JSON.parse( | ||
decodeURIComponent(req.query.authorization_details) //TODO this contains the credentials requested | ||
); | ||
const redirectUri = decodeURIComponent(req.query.redirect_uri); | ||
const nonce = req.query.nonce; | ||
const codeChallenge = decodeURIComponent(req.query.code_challenge); | ||
const codeChallengeMethod = req.query.code_challenge_method; //this should equal to S256 | ||
|
||
const clientMetadata = JSON.parse( | ||
decodeURIComponent(req.query.client_metadata) | ||
); | ||
//validations | ||
let errors = []; | ||
if (authorizationDetails.credential_definition) { | ||
console.log( | ||
`credential ${authorizationDetails.credential_definition.type} was requested` | ||
); | ||
} else { | ||
if (authorizationDetails.types) { | ||
//EBSI style | ||
console.log(`credential ${authorizationDetails.types} was requested`); | ||
} else { | ||
//errors.push("no credentials requested"); | ||
console.log(`no credentials requested`); | ||
} | ||
} | ||
|
||
if (responseType !== "code") { | ||
errors.push("Invalid response_type"); | ||
} | ||
if (!scope.includes("openid")) { | ||
errors.push("Invalid scope"); | ||
} | ||
|
||
// If validations pass, redirect with a 302 Found response | ||
const authorizationCode = null; //"SplxlOBeZQQYbYS6WxSbIA"; | ||
const codeSessions = getAuthCodeSessions(); | ||
codeSessions.requests.push({ | ||
challenge: codeChallenge, | ||
method: codeChallengeMethod, | ||
sessionId: authorizationCode, | ||
issuerState: issuerState, | ||
state: state, | ||
}); | ||
codeSessions.results.push({ | ||
sessionId: authorizationCode, | ||
issuerState: issuerState, | ||
state: state, | ||
status: "pending", | ||
}); | ||
codeSessions.walletSessions.push(state); // push state as send by wallet | ||
codeSessions.sessions.push(issuerState); | ||
|
||
// for normal response not requesting VP from wallet | ||
//const redirectUrl = `${redirectUri}?code=${authorizationCode}&state=${state}`; | ||
|
||
//5.1.5. Dynamic Credential Request https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0-12.html#name-successful-authorization-re | ||
const vpRequestJWT = buildVpRequestJwt( | ||
state, | ||
nonce, | ||
clientId, | ||
"response_uri", | ||
null, | ||
"jwk", | ||
serverURL, | ||
privateKey | ||
); | ||
const redirectUrl = `http://localhost:8080?state=${state}&client_id=${clientId}&redirect_uri=${serverURL}/direct_post_vci&response_type=id_token&response_mode=direct_post&scope=openid&nonce=${nonce}&request=${vpRequestJWT}`; | ||
|
||
if (errors.length > 0) { | ||
console.error("Validation errors:", errors); | ||
let error_description = ""; | ||
errors.forEach((element) => { | ||
error_description += element + " "; | ||
}); | ||
const encodedErrorDescription = encodeURIComponent( | ||
error_description.trim() | ||
); | ||
const errorRedirectUrl = `${redirectUri}?error=invalid_request&error_description=${encodedErrorDescription}`; | ||
//TODO mark the codeFlowSession as failed | ||
return res.redirect(302, errorRedirectUrl); | ||
} else { | ||
return res.redirect(302, redirectUrl); | ||
} | ||
}); | ||
|
||
codeFlowRouter.post("/direct_post_vci", async (req, res) => { | ||
console.log("direct_post VP for VCI is below!"); | ||
let state = req.body["state"]; | ||
let jwt = req.body["id_token"]; | ||
if (jwt) { | ||
const codeSessions = getAuthCodeSessions(); | ||
const authorizationCode = generateNonce(16); | ||
updateIssuerStateWithAuthCode( | ||
authorizationCode, | ||
state, | ||
codeSessions.walletSessions, | ||
codeSessions.results, | ||
codeSessions.requests | ||
); | ||
const redirectUrl = `http://localhost:8080?code=${authorizationCode}&state=${state}`; | ||
return res.redirect(302, redirectUrl); | ||
} else { | ||
return res.sendStatus(500); | ||
} | ||
}); | ||
|
||
function updateIssuerStateWithAuthCode( | ||
code, | ||
walletState, | ||
walletSessions, | ||
codeFlowRequestsResults, | ||
codeFlowRequests | ||
) { | ||
let index = walletSessions.indexOf(walletState); | ||
if (index >= 0) { | ||
codeFlowRequestsResults[index].sessionId = code; | ||
codeFlowRequests[index].sessionId = code; | ||
} else { | ||
console.log("issuer state will not be updated"); | ||
} | ||
} | ||
|
||
export default codeFlowRouter; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import express from "express"; | ||
import fs from "fs"; | ||
import { v4 as uuidv4 } from "uuid"; | ||
import { getAuthCodeSessions } from "../services/cacheService.js"; | ||
|
||
import qr from "qr-image"; | ||
import imageDataURI from "image-data-uri"; | ||
import { streamToBuffer } from "@jorgeferrero/stream-to-buffer"; | ||
|
||
const codeFlowRouterSDJWT = express.Router(); | ||
|
||
const serverURL = process.env.SERVER_URL || "http://localhost:3000"; | ||
|
||
|
||
|
||
// auth code flow | ||
codeFlowRouterSDJWT.get(["/offer-code-sd-jwt"], async (req, res) => { | ||
const uuid = req.query.sessionId ? req.query.sessionId : uuidv4(); | ||
const codeSessions = getAuthCodeSessions(); | ||
codeSessions.sessions.push(uuid); | ||
codeSessions.results.push({ sessionId: uuid, status: "pending" }); | ||
let credentialOffer = `openid-credential-offer://?credential_offer_uri=${serverURL}/credential-offer-code-sd-jwt/${uuid}`; | ||
|
||
let code = qr.image(credentialOffer, { | ||
type: "png", | ||
ec_level: "H", | ||
size: 10, | ||
margin: 10, | ||
}); | ||
let mediaType = "PNG"; | ||
let encodedQR = imageDataURI.encode(await streamToBuffer(code), mediaType); | ||
res.json({ | ||
qr: encodedQR, | ||
deepLink: credentialOffer, | ||
sessionId: uuid, | ||
}); | ||
}); | ||
|
||
// auth code-flow request | ||
codeFlowRouterSDJWT.get(["/credential-offer-code-sd-jwt/:id"], (req, res) => { | ||
res.json({ | ||
credential_issuer: serverURL, | ||
credentials: ["VerifiablePortableDocumentA1"], | ||
grants: { | ||
authorization_code: { | ||
issuer_state: req.params.id, | ||
}, | ||
}, | ||
}); | ||
}); | ||
|
||
export default codeFlowRouterSDJWT; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import express from "express"; | ||
import fs from "fs"; | ||
import { pemToJWK } from "../utils/cryptoUtils.js"; | ||
const metadataRouter = express.Router(); | ||
|
||
const serverURL = process.env.SERVER_URL || "http://localhost:3000"; | ||
|
||
const privateKey = fs.readFileSync("./private-key.pem", "utf-8"); | ||
const publicKeyPem = fs.readFileSync("./public-key.pem", "utf-8"); | ||
|
||
const issuerConfig = JSON.parse( | ||
fs.readFileSync("./data/issuer-config.json", "utf-8") | ||
); | ||
const oauthConfig = JSON.parse( | ||
fs.readFileSync("./data/oauth-config.json", "utf-8") | ||
); | ||
|
||
const jwks = pemToJWK(publicKeyPem, "public"); | ||
|
||
metadataRouter.get( | ||
"/.well-known/openid-credential-issuer", | ||
async (req, res) => { | ||
issuerConfig.credential_issuer = serverURL; | ||
issuerConfig.authorization_server = serverURL; | ||
issuerConfig.credential_endpoint = serverURL + "/credential"; | ||
issuerConfig.deferred_credential_endpoint = | ||
serverURL + "/credential_deferred"; | ||
|
||
res.type("application/json").send(issuerConfig); | ||
} | ||
); | ||
|
||
metadataRouter.get( | ||
[ | ||
"/.well-known/oauth-authorization-server", | ||
"/.well-known/openid-configuration", | ||
"/oauth-authorization-server/rfc-issuer", //this is required in case the issuer is behind a reverse proxy: see https://www.rfc-editor.org/rfc/rfc8414.html | ||
], | ||
async (req, res) => { | ||
oauthConfig.issuer = serverURL; | ||
oauthConfig.authorization_endpoint = serverURL + "/authorize"; | ||
oauthConfig.token_endpoint = serverURL + "/token_endpoint"; | ||
oauthConfig.jwks_uri = serverURL + "/jwks"; | ||
res.type("application/json").send(oauthConfig); | ||
} | ||
); | ||
|
||
metadataRouter.get(["/", "/jwks"], (req, res) => { | ||
res.json({ | ||
keys: [ | ||
{ ...jwks, kid: `aegean#authentication-key`, use: "sig" }, | ||
{ ...jwks, kid: `aegean#authentication-key`, use: "keyAgreement" }, //key to encrypt the sd-jwt response]) | ||
], | ||
}); | ||
}); | ||
|
||
|
||
export default metadataRouter; |
Oops, something went wrong.