Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New release #106

Merged
merged 9 commits into from
Apr 25, 2024
Merged
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
8 changes: 4 additions & 4 deletions .github/workflows/build-test-on-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,16 @@ jobs:
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: '18.18.0'
- uses: pnpm/action-setup@v2
node-version: '20.x'
- uses: pnpm/action-setup@v3
with:
version: 8
version: 8.15.7
- run: pnpm install
- run: pnpm build
- name: run CI tests
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/build-test-publish-on-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,16 +30,16 @@ jobs:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: '18.18.0'
- uses: pnpm/action-setup@v2
node-version: '20.x'
- uses: pnpm/action-setup@v3
with:
version: 8
version: 8.15.7
# - name: Get yarn cache directory path
# id: yarn-cache-dir-path
# run: echo "::set-output name=dir::$(yarn cache dir)"
Expand Down
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

[![CI](https://github.com/Sphereon-Opensource/OID4VCI/actions/workflows/build-test-on-pr.yml/badge.svg)](https://github.com/Sphereon-Opensource/OID4VCI/actions/workflows/build-test-on-pr.yml) [![codecov](https://codecov.io/gh/Sphereon-Opensource/OID4VCI/branch/develop/graph/badge.svg)](https://codecov.io/gh/Sphereon-Opensource/OID4VCI) [![NPM Version](https://img.shields.io/npm/v/@sphereon/oid4vci-client.svg)](https://npm.im/@sphereon/oid4vci-client)

_IMPORTANT the packages are still in an early development stage, as such breaking changes are to be expected_
_IMPORTANT the packages are still in an early development stage, which means that breaking changes are to be expected_

# Background

Expand All @@ -19,10 +19,10 @@ OpenID4VCI defines an API designated as Credential Endpoint that is used to issu
corresponding OAuth 2.0 based authorization mechanisms (see [RFC6749]) that a Wallet uses to obtain authorization to
receive verifiable credentials. W3C formats as well as other Credential formats are supported. This allows existing
OAuth 2.0 deployments and OpenID Connect OPs (see [OpenID.Core]) to extend their service and become Credential Issuers.
It also allows new applications built using Verifiable Credentials to utilize OAuth 2.0 as integration and
It also allows new applications built using Verifiable Credentials to utilize OAuth 2.0 as an integration and
interoperability layer. This package provides holder/wallet support to interact with OpenID4VCI capable Issuer systems.

Next to the client and issuer, there is also a common package, which has all the types and payloads shared between the client and issuer.
In addition to the client and issuer, there is also a common package, which has all the types and payloads shared between the client and issuer.

# Packages
There are 2 main packages in this mono-repository
Expand All @@ -34,7 +34,7 @@ The OpenID4VCI client is typically used in wallet type of applications, where th
## OpenID for VCI Issuer

The OpenID4VCI issuer is used in issuer type applications, where an organization is issuing the credential(s). More info can be found in the issuer [README](./packages/issuer/README.md).
Please not that the Issuer is a library. It has some examples how to run it with REST endpoints. If you however are looking for a full solution we suggest our [SSI SDK](https://github.com/Sphereon-Opensource/ssi-sdk) or the [demo](https://github.com/Sphereon-Opensource/OID4VC-demo)
Please note that the Issuer is a library. It has some examples on how to run it with REST endpoints. If you are however looking for a full solution we suggest our [SSI SDK](https://github.com/Sphereon-Opensource/ssi-sdk) or the [demo](https://github.com/Sphereon-Opensource/OID4VC-demo)


# Flows
Expand All @@ -47,9 +47,9 @@ This flow is supported but might need more work, so you might run into issues tr

## Pre-authorized Code Flow

The pre-authorized code flow assumes the user is using an out of bound mechanism outside the issuance flow to
The pre-authorized code flow assumes that the user is using an out of bound mechanism outside the issuance flow to
authenticate first.

The below diagram shows the steps involved in the pre-authorized code flow. Note that wallet inner functionalities (like
saving VCs) are out of scope for this library. Also This library doesn't involve any functionalities of a VC Issuer
The below diagram shows the steps involved in the pre-authorized code flow. Note that inner wallet functionalities (like
saving VCs) are out of scope for this library. Also This library doesn't include any functionalities of a VC Issuer
![Flow diagram](https://www.plantuml.com/plantuml/proxy?cache=no&src=https://raw.githubusercontent.com/Sphereon-Opensource/OID4VCI-client/develop/docs/preauthorized-code-flow.puml)
10 changes: 5 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,13 @@
"@types/node": "^18.17.3",
"codecov": "^3.8.3",
"jest": "^29.6.2",
"lerna": "^7.1.4",
"lerna": "^8.1.2",
"lerna-changelog": "^2.2.0",
"npm-run-all": "^4.1.5",
"prettier": "^3.0.1",
"rimraf": "^5.0.1",
"ts-jest": "^29.1.1",
"typescript": "5.3.3"
"prettier": "^3.2.5",
"rimraf": "^5.0.5",
"ts-jest": "^29.1.2",
"typescript": "5.4.5"
},
"keywords": [
"Sphereon",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ async function proofOfPossessionCallbackFunction(args: Jwt, kid?: string): Promi
}
return await new jose.SignJWT({ ...args.payload })
.setProtectedHeader({ ...args.header })
.setIssuedAt(+new Date())
.setIssuedAt(args.payload.iat ?? Math.round(+new Date()/1000))
.setIssuer(kid)
.setAudience(args.payload.aud)
.setExpirationTime('2h')
Expand Down
4 changes: 2 additions & 2 deletions packages/client/lib/AccessTokenClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ export class AccessTokenClient {
metadata: metadata
? metadata
: issuerOpts?.fetchMetadata
? await MetadataClient.retrieveAllMetadata(issuerOpts.issuer, { errorOnNotFound: false })
: undefined,
? await MetadataClient.retrieveAllMetadata(issuerOpts.issuer, { errorOnNotFound: false })
: undefined,
});

return this.sendAuthCode(requestTokenURL, accessTokenRequest);
Expand Down
4 changes: 2 additions & 2 deletions packages/client/lib/CredentialOfferClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ export class CredentialOfferClient {
uriTypeProperties: isUri
? ['credential_offer_uri']
: version >= OpenId4VCIVersion.VER_1_0_11
? ['credential_issuer', 'credential_type']
: ['issuer', 'credential_type'],
? ['credential_issuer', 'credential_type']
: ['issuer', 'credential_type'],
param,
version,
});
Expand Down
10 changes: 6 additions & 4 deletions packages/client/lib/OpenID4VCIClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,8 +306,9 @@ export class OpenID4VCIClient {
} else if (!response.successBody) {
debug(`Access token error. No success body`);
throw Error(
`Retrieving an access token from ${this._state.endpointMetadata
?.token_endpoint} for issuer ${this.getIssuer()} failed as there was no success response body`,
`Retrieving an access token from ${
this._state.endpointMetadata?.token_endpoint
} for issuer ${this.getIssuer()} failed as there was no success response body`,
);
}
this._state.accessTokenResponse = response.successBody;
Expand Down Expand Up @@ -428,8 +429,9 @@ export class OpenID4VCIClient {
} else if (!response.successBody) {
debug(`Credential request error. No success body`);
throw Error(
`Retrieving a credential from ${this._state.endpointMetadata
?.credential_endpoint} for issuer ${this.getIssuer()} failed as there was no success response body`,
`Retrieving a credential from ${
this._state.endpointMetadata?.credential_endpoint
} for issuer ${this.getIssuer()} failed as there was no success response body`,
);
}
return response.successBody;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { IDENTIPROOF_ISSUER_URL } from './MetadataMocks';

const jwt: Jwt = {
header: { alg: Alg.ES256, kid: 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1', typ: 'jwt' },
payload: { iss: 'sphereon:wallet', nonce: 'tZignsnFbp', jti: 'tZignsnFbp223', aud: IDENTIPROOF_ISSUER_URL, iat: Date.now() },
payload: { iss: 'sphereon:wallet', nonce: 'tZignsnFbp', jti: 'tZignsnFbp223', aud: IDENTIPROOF_ISSUER_URL, iat: Date.now()/1000 },
};

const kid = 'did:example:ebfeb1f712ebc6f1c276e12ec21/keys/1';
Expand Down
2 changes: 1 addition & 1 deletion packages/client/lib/__tests__/SdJwt.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ const vcIssuer = new VcIssuerBuilder()
},
payload: {
aud: issuerMetadata.credential_issuer,
iat: +new Date(),
iat: +new Date()/1000,
nonce: 'a-c-nonce',
},
},
Expand Down
4 changes: 2 additions & 2 deletions packages/client/lib/functions/ProofUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ const createJWT = (jwtProps?: JwtProps, existingJwt?: Jwt): Jwt => {
const now = +new Date();
const jwtPayload: Partial<JWTPayload> = {
aud,
iat: jwt.payload?.iat ? jwt.payload.iat : now / 1000 - 60, // Let's ensure we subtract 60 seconds for potential time offsets
exp: jwt.payload?.exp ? jwt.payload.exp : now / 1000 + 10 * 60,
iat: jwt.payload?.iat ?? Math.round(now / 1000 - 60), // Let's ensure we subtract 60 seconds for potential time offsets
exp: jwt.payload?.exp ?? Math.round(now / 1000 + 10 * 60),
nonce,
...(iss ? { iss } : {}),
...(jti ? { jti } : {}),
Expand Down
4 changes: 2 additions & 2 deletions packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
},
"dependencies": {
"@sphereon/oid4vci-common": "workspace:*",
"@sphereon/ssi-types": "^0.18.1",
"@sphereon/ssi-types": "^0.23.0",
"cross-fetch": "^3.1.8",
"debug": "^4.3.4"
},
"devDependencies": {
"@sphereon/ssi-sdk-ext.key-utils": "^0.16.0",
"@sphereon/ssi-sdk-ext.key-utils": "^0.18.2",
"@transmute/did-key.js": "^0.3.0-unstable.10",
"@trust/keyto": "^2.0.0-alpha1",
"@types/jest": "^29.5.3",
Expand Down
4 changes: 2 additions & 2 deletions packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"build:clean": "tsc --build --clean && tsc --build"
},
"dependencies": {
"@sphereon/ssi-types": "^0.18.1",
"@sphereon/ssi-types": "^0.23.0",
"cross-fetch": "^3.1.8",
"jwt-decode": "^3.1.2",
"sha.js": "^2.4.11",
Expand All @@ -19,7 +19,7 @@
"devDependencies": {
"@types/jest": "^29.5.3",
"@types/sha.js": "^2.4.4",
"typescript": "5.3.3"
"typescript": "5.4.5"
},
"peerDependencies": {
"msrcrypto": "^1.5.8"
Expand Down
4 changes: 2 additions & 2 deletions packages/issuer-rest/lib/IssuerTokenEndpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import { v4 } from 'uuid'
* @param interval
*/
export const handleTokenRequest = <T extends object>({
tokenExpiresIn,
tokenExpiresIn, // expiration in seconds
accessTokenSignerCallback,
accessTokenIssuer,
cNonceExpiresIn,
cNonceExpiresIn, // expiration in seconds
issuer,
interval,
}: Required<Pick<ITokenEndpointOpts, 'accessTokenIssuer' | 'cNonceExpiresIn' | 'interval' | 'accessTokenSignerCallback' | 'tokenExpiresIn'>> & {
Expand Down
2 changes: 1 addition & 1 deletion packages/issuer-rest/lib/__tests__/ClientIssuerIT.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ describe('VcIssuer', () => {
async function proofOfPossessionCallbackFunction(args: Jwt, kid?: string): Promise<string> {
return await new jose.SignJWT({ ...args.payload })
.setProtectedHeader({ ...args.header })
.setIssuedAt(+new Date())
.setIssuedAt(args.payload.iat ?? Math.round(+new Date()/1000))
.setIssuer(kid!)
.setAudience(args.payload.aud!)
.setExpirationTime('2h')
Expand Down
10 changes: 5 additions & 5 deletions packages/issuer-rest/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@
"dependencies": {
"@sphereon/oid4vci-common": "workspace:*",
"@sphereon/oid4vci-issuer": "workspace:*",
"@sphereon/ssi-express-support": "^0.18.1",
"@sphereon/ssi-types": "^0.18.1",
"@sphereon/ssi-express-support": "^0.23.0",
"@sphereon/ssi-types": "^0.23.0",
"body-parser": "^1.20.2",
"cookie-parser": "^1.4.6",
"cors": "^2.8.5",
"dotenv-flow": "^3.2.0",
"express": "^4.18.2",
"dotenv-flow": "^3.3.0",
"express": "^4.19.2",
"http-terminator": "^3.2.0",
"typescript": "5.3.3",
"typescript": "5.4.5",
"uuid": "^9.0.0"
},
"devDependencies": {
Expand Down
9 changes: 5 additions & 4 deletions packages/issuer/lib/VcIssuer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,8 @@ export class VcIssuer<DIDDoc extends object> {
credentialDataSupplier?: CredentialDataSupplier
credentialDataSupplierInput?: CredentialDataSupplierInput
newCNonce?: string
cNonceExpiresIn?: number
tokenExpiresIn?: number
cNonceExpiresIn?: number // expiration duration in seconds
tokenExpiresIn?: number // expiration duration in seconds
jwtVerifyCallback?: JWTVerifyCallback<DIDDoc>
credentialSignerCallback?: CredentialSignerCallback<DIDDoc>
responseCNonce?: string
Expand Down Expand Up @@ -417,7 +417,7 @@ export class VcIssuer<DIDDoc extends object> {
tokenExpiresIn,
}: {
credentialRequest: UniformCredentialRequest
tokenExpiresIn: number
tokenExpiresIn: number // expiration duration in seconds
// grants?: Grant,
clientId?: string
jwtVerifyCallback?: JWTVerifyCallback<DIDDoc>
Expand Down Expand Up @@ -519,7 +519,8 @@ export class VcIssuer<DIDDoc extends object> {
}
if (!iat) {
throw new Error(IAT_ERROR)
} else if (iat > createdAt + tokenExpiresIn) {
} else if (iat > Math.round(createdAt/1000) + tokenExpiresIn) {
// createdAt is in milliseconds whilst iat and tokenExpiresIn are in seconds
throw new Error(IAT_ERROR)
}
// todo: Add a check of iat against current TS on server with a skew
Expand Down
6 changes: 3 additions & 3 deletions packages/issuer/lib/__tests__/VcIssuer.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ describe('VcIssuer', () => {
},
payload: {
aud: IDENTIPROOF_ISSUER_URL,
iat: +new Date(),
iat: +new Date()/1000,
nonce: 'test-nonce',
},
},
Expand Down Expand Up @@ -322,7 +322,7 @@ describe('VcIssuer', () => {
},
payload: {
aud: IDENTIPROOF_ISSUER_URL,
iat: +new Date(),
iat: +new Date()/1000,
nonce: 'test-nonce',
},
},
Expand Down Expand Up @@ -405,7 +405,7 @@ describe('VcIssuer', () => {
},
payload: {
aud: IDENTIPROOF_ISSUER_URL,
iat: +new Date(),
iat: +new Date()/1000,
nonce: 'test-nonce',
},
},
Expand Down
4 changes: 2 additions & 2 deletions packages/issuer/lib/functions/CredentialOfferUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,9 @@ export function createCredentialOfferURI(
return createCredentialOfferURIFromObject(credentialOffer, opts)
}

export const isPreAuthorizedCodeExpired = (state: CredentialOfferSession, expirationDuration: number) => {
export const isPreAuthorizedCodeExpired = (state: CredentialOfferSession, expirationDurationInSeconds: number) => {
const now = +new Date()
const expirationTime = state.createdAt + expirationDuration
const expirationTime = state.createdAt + expirationDurationInSeconds * 1000
return now >= expirationTime
}

Expand Down
4 changes: 2 additions & 2 deletions packages/issuer/lib/tokens/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ export const createAccessTokenResponse = async (
credentialOfferSessions: IStateManager<CredentialOfferSession>
cNonces: IStateManager<CNonceState>
cNonce?: string
cNonceExpiresIn?: number
tokenExpiresIn: number
cNonceExpiresIn?: number // expiration in seconds
tokenExpiresIn: number // expiration in seconds
// preAuthorizedCodeExpirationDuration?: number
accessTokenSignerCallback: JWTSignerCallback
accessTokenIssuer: string
Expand Down
6 changes: 3 additions & 3 deletions packages/issuer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
},
"dependencies": {
"@sphereon/oid4vci-common": "workspace:*",
"@sphereon/ssi-types": "^0.18.1",
"@sphereon/ssi-types": "^0.23.0",
"uuid": "^9.0.0"
},
"peerDependencies": {
Expand All @@ -28,10 +28,10 @@
"devDependencies": {
"@sphereon/oid4vci-client": "workspace:*",
"@types/jest": "^29.5.3",
"@types/node": "^18.17.3",
"@types/node": "^18.19.31",
"@types/uuid": "^9.0.2",
"did-resolver": "^4.1.0",
"typescript": "5.3.3"
"typescript": "5.4.5"
},
"engines": {
"node": ">=18"
Expand Down
Loading