Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions generate_claim_token_from_access_grant.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
const claimToken = {
"@context": [
"https://www.w3.org/2018/credentials/v1"
],
"type": [
"VerifiablePresentation"
],
"verifiableCredential": []
}

const accessGrant = {
"id": "https://vc.sandbox-pod.datanutsbedrijf.be/vc/65741eb1-9b74-4258-91e8-9a69b88809fa",
"type": [
"VerifiableCredential",
"SolidAccessGrant"
],
"proof": {
"type": "Ed25519Signature2020",
"created": "2025-04-01T08:01:13.503Z",
"domain": "solid",
"proofPurpose": "assertionMethod",
"proofValue": "z5qmtJ6KeSZ2zw9PJ4jMNqQoMxPEmrH7vHecUQvLtSwsnxB6cSJFXZFB6v4dSL6qjZa5pNS77HcdEmqSW4GH4ToJz",
"verificationMethod": "https://vc.sandbox-pod.datanutsbedrijf.be/key/b7356870-a180-3bfb-bea9-f9a13cd7b04e"
},
"credentialStatus": {
"id": "https://vc.sandbox-pod.datanutsbedrijf.be/status/oLch#0",
"type": "RevocationList2020Status",
"revocationListCredential": "https://vc.sandbox-pod.datanutsbedrijf.be/status/oLch",
"revocationListIndex": "0"
},
"credentialSubject": {
"id": "https://tni.webid.burgerprofiel.dev-vlaanderen.be/profiles/05be274a-21bf-4b25-8900-1899e0599316",
"providedConsent": {
"mode": [
"Read",
"Append",
"Write"
],
"forPersonalData": "https://storage.sandbox-pod.datanutsbedrijf.be/6a43495b-98fb-4df8-a608-69225cec06c3/book_index",
"hasStatus": "ConsentStatusExplicitlyGiven",
"isProvidedTo": "https://id.we-are-acc.vito.be/24718a19-4d75-4925-b99c-db103bfba9f5"
}
},
"expirationDate": "2025-09-28T08:01:13.489713189Z",
"issuanceDate": "2025-04-01T08:01:13.489Z",
"issuer": "https://vc.sandbox-pod.datanutsbedrijf.be",
"@context": [
"https://www.w3.org/2018/credentials/v1",
"https://schema.inrupt.com/credentials/v2.jsonld",
"https://w3id.org/security/data-integrity/v1",
"https://w3id.org/vc-revocation-list-2020/v1",
"https://w3id.org/vc/status-list/2021/v1",
"https://w3id.org/security/suites/ed25519-2020/v1"
]
}

claimToken.verifiableCredential.push(accessGrant)

console.log(Buffer.from(JSON.stringify(claimToken)).toString('base64'));
32 changes: 32 additions & 0 deletions generate_code_verifier_and_challenge.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { createHash, randomBytes } from "crypto";

let encode;
if (Buffer.isEncoding('base64url')) {
encode = (input, encoding = 'utf8') => Buffer.from(input, encoding).toString('base64url');
} else {
const fromBase64 = (base64) => base64.replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
encode = (input, encoding = 'utf8') =>
fromBase64(Buffer.from(input, encoding).toString('base64'));
}

const decode = (input) => Buffer.from(input, 'base64');

const random = (bytes = 32) => encode(randomBytes(bytes));

const state = random;
const nonce = random;
const codeVerifier = random;
const codeChallenge = (codeVerifier) =>
encode(createHash('sha256').update(codeVerifier).digest())

const stateValue = random();
const nonceValue = random();
const codeVerifierValue = random();
const codeChallengeValue = codeChallenge(codeVerifierValue);

console.log(`
State Value: ${stateValue}
Nonce Value: ${nonceValue}
Code Verifier Value: ${codeVerifierValue}
Code Challenge Value: ${codeChallengeValue}
`);
64 changes: 57 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,13 @@
"@vito-nv/weare-core": "^1.0.0",
"@vito-nv/weare-expressjs": "^1.0.0",
"cors": "^2.8.5",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"express-http-context": "^1.2.4",
"express-session": "^1.18.1",
"jose": "^6.0.10",
"loglevel": "^1.9.2",
"dotenv": "^16.4.7"
"uuid": "^11.1.0"
},
"devDependencies": {
"@types/express": "^5.0.0",
Expand Down
4 changes: 4 additions & 0 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import {sessionEndpoint} from "./endpoint/session-endpoint";
import vcEndpoint from "./endpoint/vc-endpoint";
import {initializeEnvironment} from "./validate/environment-validate";
import path from "path";
import {oidcEndpoint} from "./endpoint/oidc-endpoint";
import {htiEndpoint} from "./endpoint/hti-endpoints";

// Load environment variables from the `.env` file into `process.env`
dotEnv.config();
Expand Down Expand Up @@ -106,6 +108,8 @@ app.use(bodyParser.raw());
* These functions define routes and their handlers for the application.
*/
authenticationEndpoint(app);
oidcEndpoint(app)
htiEndpoint(app)
podEndpoint(app);
sessionEndpoint(app);
vcEndpoint(app);
Expand Down
5 changes: 2 additions & 3 deletions src/endpoint/authentication-endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,10 @@ export function authenticationEndpoint(app: Express) {
if(session?.info.isLoggedIn)
await session.logout({logoutType: 'app'});

if (!session)
if (!session) {
session = new Session( {storage: globalThis.solidStorage!, keepAlive: false});

if(!req.session.solidSid)
req.session.solidSid = session.info.sessionId;
}

if (req.query.redirectUrl)
req.session.redirectUrl = (new URL(req.query.redirectUrl as string)).href;
Expand Down
29 changes: 29 additions & 0 deletions src/endpoint/hti-endpoints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {Express} from "express";
import {createLaunchToken} from "../service/hti-service";
import {getSessionMandatory} from "@vito-nv/weare-expressjs";
import log from "loglevel";

export function htiEndpoint(app: Express) {
app.get("/create-hti", async (req, res, next) => {
log.debug("Endpoint '/create-hti' called");

if (!req.query.webId || !req.query.redirectUrl) {
res.status(400).send("Missing webId or redirectUrl in the request query parameters.");
return;
}

next();
}, getSessionMandatory.bind({ storage: globalThis.solidStorage }), async (req, res) => {
let launchToken = await createLaunchToken(
new URL(`${process.env['PROTOCOL']}://${req.get("host")}`), // This is required due to intermediate proxies that might perform TLS offloading
req.query.webId as string,
new URL(req.query.redirectUrl as string)
);
res.json({
launchToken
});

return;
});

}
30 changes: 30 additions & 0 deletions src/endpoint/oidc-endpoint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {Express} from "express";
import {getResource, getSessionOptional} from "@vito-nv/weare-expressjs";
import {getKeyPair} from "../service/keypair.service";

export function oidcEndpoint(app: Express) {

app.get("/.well-known/oidc-configuration", async (req, res, next) => {
next();
}, async (req, res, next) => {
let hostHeader = req.header('host');

const oidcConfiguration = {
"issuer": `${req.protocol}://${hostHeader}`,
"jwks_uri": `${req.protocol}://${hostHeader}/.well-known/jwks.json`
}
res.json(oidcConfiguration);
});

app.get("/.well-known/jwks.json", async (req, res, next) => {
next();
}, async (req, res, next) => {
let keyPair = await getKeyPair();
const jwks = {
keys: [
keyPair.publicKey
]
}
res.json(jwks)
})
}
19 changes: 19 additions & 0 deletions src/service/hti-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {SignJWT} from "jose";
import {getKeyPair} from "./keypair.service";
import {v4} from "uuid";

export async function createLaunchToken(issuer: URL, subject: string, recipient: URL) {
let keyPair = await getKeyPair();
return await new SignJWT({})
.setIssuer(issuer.href)
.setAudience(recipient.href)
.setSubject(subject)
.setExpirationTime("15m")
.setProtectedHeader({alg: "RS512", typ: "JWT", kid: keyPair.publicKey.kid})
.setIssuedAt()
.setNotBefore(
"10s"
)
.setJti(v4())
.sign(keyPair.privateKey);
}
35 changes: 35 additions & 0 deletions src/service/keypair.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import {calculateJwkThumbprint, exportJWK, generateKeyPair} from "jose";
import type * as types from "jose/dist/types/types";

interface KeyPair {
publicKey: types.JWK
privateKey: types.JWK
}

var keyPair: KeyPair

export async function getKeyPair(): Promise<KeyPair> {
if (!keyPair) {
keyPair = await createKeyPairInternal()
}
return keyPair
}

async function createKeyPairInternal(): Promise<KeyPair> {
const algorithm = 'RS512';
const { publicKey, privateKey } = await generateKeyPair(algorithm, {
modulusLength: 2048,
extractable: true,
});

const publicJwk = await exportJWK(publicKey)
const privateJwk = await exportJWK(privateKey)
publicJwk.alg = algorithm
publicJwk.kid = await calculateJwkThumbprint(publicJwk)

privateJwk.alg = algorithm
return {
publicKey: publicJwk,
privateKey: privateJwk
}
}