Skip to content

Commit 9ced4d9

Browse files
authored
fix: add tx code support (#312)
Signed-off-by: Mirko Mollik <mirko.mollik@eudi.sprind.org>
1 parent 9861d5d commit 9ced4d9

File tree

10 files changed

+178
-62
lines changed

10 files changed

+178
-62
lines changed

apps/backend/src/issuer/authorize/authorize.controller.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,7 @@ export class AuthorizeController {
8383
@Req() req: Request,
8484
@Param("tenantId") tenantId: string,
8585
): Promise<any> {
86-
return this.authorizeService
87-
.validateTokenRequest(body, req, tenantId)
88-
.catch((error) => {
89-
console.error("Error in token endpoint:", error);
90-
throw error;
91-
});
86+
return this.authorizeService.validateTokenRequest(body, req, tenantId);
9287
}
9388

9489
/**

apps/backend/src/issuer/authorize/authorize.service.ts

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { randomUUID } from "node:crypto";
2-
import { ConflictException, Injectable } from "@nestjs/common";
2+
import {
3+
BadRequestException,
4+
ConflictException,
5+
Injectable,
6+
} from "@nestjs/common";
37
import { ConfigService } from "@nestjs/config";
48
import {
59
type AuthorizationCodeGrantIdentifier,
@@ -107,11 +111,19 @@ export class AuthorizeService {
107111
}
108112
}
109113

114+
/**
115+
* Validate the token request.
116+
* This endpoint is used to exchange the authorization code for an access token.
117+
* @param body
118+
* @param req
119+
* @returns
120+
*/
110121
async validateTokenRequest(
111122
body: any,
112123
req: Request,
113124
tenantId: string,
114125
): Promise<any> {
126+
//TODO: check if all the error cases are covered: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-token-error-response
115127
const url = `${this.configService.getOrThrow<string>("PUBLIC_URL")}${req.url}`;
116128
const parsedAccessTokenRequest = this.getAuthorizationServer(
117129
tenantId,
@@ -143,29 +155,34 @@ export class AuthorizeService {
143155
parsedAccessTokenRequest.grant.grantType ===
144156
preAuthorizedCodeGrantIdentifier
145157
) {
146-
const { dpop } = await this.getAuthorizationServer(
147-
tenantId,
148-
).verifyPreAuthorizedCodeAccessTokenRequest({
149-
grant: parsedAccessTokenRequest.grant as ParsedAccessTokenPreAuthorizedCodeRequestGrant,
150-
accessTokenRequest: parsedAccessTokenRequest.accessTokenRequest,
151-
request: {
152-
method: req.method as HttpMethod,
153-
url,
154-
headers: getHeadersFromRequest(req),
155-
},
156-
dpop: {
157-
required: issuanceConfig.dPopRequired,
158-
allowedSigningAlgs:
159-
authorizationServerMetadata.dpop_signing_alg_values_supported,
160-
jwt: parsedAccessTokenRequest.dpop?.jwt,
161-
},
158+
const { dpop } = await this.getAuthorizationServer(tenantId)
159+
.verifyPreAuthorizedCodeAccessTokenRequest({
160+
grant: parsedAccessTokenRequest.grant as ParsedAccessTokenPreAuthorizedCodeRequestGrant,
161+
accessTokenRequest:
162+
parsedAccessTokenRequest.accessTokenRequest,
163+
request: {
164+
method: req.method as HttpMethod,
165+
url,
166+
headers: getHeadersFromRequest(req),
167+
},
168+
dpop: {
169+
required: issuanceConfig.dPopRequired,
170+
allowedSigningAlgs:
171+
authorizationServerMetadata.dpop_signing_alg_values_supported,
172+
jwt: parsedAccessTokenRequest.dpop?.jwt,
173+
},
162174

163-
authorizationServerMetadata,
175+
authorizationServerMetadata,
164176

165-
expectedPreAuthorizedCode:
166-
parsedAccessTokenRequest.grant.preAuthorizedCode,
167-
expectedTxCode: parsedAccessTokenRequest.grant.txCode,
168-
});
177+
expectedPreAuthorizedCode: session.authorization_code!,
178+
expectedTxCode: session.credentialPayload?.tx_code,
179+
})
180+
.catch((err) => {
181+
throw new BadRequestException(err.error, {
182+
cause: err,
183+
description: err.error_description,
184+
});
185+
});
169186
dpopValue = dpop;
170187
}
171188

apps/backend/src/issuer/oid4vci/dto/offer-request.dto.ts

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,44 @@ export class OfferRequestDto {
125125
* Each credential can have claims provided inline or fetched via webhook.
126126
* Keys must be a subset of credentialConfigurationIds.
127127
*/
128+
@ApiProperty({
129+
description:
130+
"Credential claims configuration per credential. Keys must match credentialConfigurationIds.",
131+
type: "object",
132+
properties: {
133+
additionalProperties: {
134+
oneOf: [
135+
{
136+
type: "object",
137+
properties: {
138+
type: { type: "string", enum: ["inline"] },
139+
claims: {
140+
type: "object",
141+
additionalProperties: true,
142+
},
143+
},
144+
required: ["type", "claims"],
145+
},
146+
{
147+
type: "object",
148+
properties: {
149+
type: { type: "string", enum: ["webhook"] },
150+
webhook: { type: "object" },
151+
},
152+
required: ["type", "webhook"],
153+
},
154+
],
155+
},
156+
},
157+
example: {
158+
citizen: {
159+
type: "inline",
160+
claims: { given_name: "John", family_name: "Doe" },
161+
},
162+
},
163+
})
128164
@IsObject()
129165
@IsOptional()
130-
@ValidateNested({ each: true })
131166
@Validate(CredentialClaimsMatchIdsConstraint)
132167
@Transform(({ value }) => {
133168
if (!value) return value;
@@ -136,9 +171,13 @@ export class OfferRequestDto {
136171
for (const [key, val] of Object.entries(value)) {
137172
const source = val as any;
138173
if (source.type === "inline") {
139-
result[key] = plainToClass(InlineClaimsSource, val);
174+
result[key] = plainToClass(InlineClaimsSource, val, {
175+
enableImplicitConversion: true,
176+
});
140177
} else if (source.type === "webhook") {
141-
result[key] = plainToClass(WebhookClaimsSource, val);
178+
result[key] = plainToClass(WebhookClaimsSource, val, {
179+
enableImplicitConversion: true,
180+
});
142181
}
143182
}
144183
return result;

apps/backend/src/issuer/oid4vci/oid4vci.service.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,10 +192,20 @@ export class Oid4vciService {
192192
let grants: any;
193193
const issuer_state = v4();
194194
if (body.flow === FlowType.PRE_AUTH_CODE) {
195+
//check if tx_code is a number
195196
authorization_code = v4();
196197
grants = {
197198
[preAuthorizedCodeGrantIdentifier]: {
198199
"pre-authorized_code": authorization_code,
200+
tx_code: body.tx_code
201+
? {
202+
input_mode: Number(body.tx_code)
203+
? "numeric"
204+
: "text",
205+
length: body.tx_code.length,
206+
//TODO: should give the possiblity to pass a description
207+
}
208+
: undefined,
199209
},
200210
};
201211
} else {
@@ -311,8 +321,7 @@ export class Oid4vciService {
311321
issuerMetadata,
312322
credentialRequest: req.body as Record<string, unknown>,
313323
});
314-
} catch (error) {
315-
console.log(error);
324+
} catch {
316325
throw new ConflictException("Invalid credential request");
317326
}
318327

apps/client/src/app/issuance/credential-config/credential-config-create/credential-config-create.component.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import {
2929
import { EditorComponent, extractSchema } from '../../../utils/editor/editor.component';
3030
import { ImageFieldComponent } from '../../../utils/image-field/image-field.component';
3131
import {
32-
fbWebhook,
32+
createWebhookFormGroup,
3333
WebhookConfigEditComponent,
3434
} from '../../../utils/webhook-config-edit/webhook-config-edit.component';
3535

@@ -93,8 +93,8 @@ export class CredentialConfigCreateComponent implements OnInit {
9393
schema: new FormControl(''),
9494
displayConfigs: new FormArray([this.createDisplayConfigGroup()]),
9595
embeddedDisclosurePolicy: new FormControl(''),
96-
claimsWebhook: fbWebhook,
97-
notificationWebhook: fbWebhook,
96+
claimsWebhook: createWebhookFormGroup(),
97+
notificationWebhook: createWebhookFormGroup(),
9898
} as { [k in keyof Omit<CredentialConfigCreate, 'config'>]: any });
9999

100100
if (this.route.snapshot.params['id']) {
@@ -195,12 +195,14 @@ export class CredentialConfigCreateComponent implements OnInit {
195195
keyBinding: config.keyBinding ?? true,
196196
statusManagement: config.statusManagement ?? true,
197197
claims: this.stringifyField(config.claims),
198+
claimsWebhook: config.claimsWebhook,
199+
notificationWebhook: config.notificationWebhook,
198200
disclosureFrame: this.stringifyField(config.disclosureFrame),
199201
vct: this.stringifyField(config.vct),
200202
schema: this.stringifyField(config.schema),
201203
displayConfigs: config.config?.display || [],
202204
embeddedDisclosurePolicy: this.stringifyField(config.embeddedDisclosurePolicy),
203-
});
205+
} as { [k in keyof Omit<CredentialConfigCreate, 'config'>]: any });
204206
}
205207

206208
private markFormGroupTouched(): void {

apps/client/src/app/issuance/issuance-config/issuance-config-create/issuance-config-create.component.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { issuanceConfigSchema } from '../../../utils/schemas';
2020
import { JsonViewDialogComponent } from '../../credential-config/credential-config-create/json-view-dialog/json-view-dialog.component';
2121
import { MatDialog } from '@angular/material/dialog';
2222
import {
23-
fbWebhook,
23+
createWebhookFormGroup,
2424
WebhookConfigEditComponent,
2525
} from '../../../utils/webhook-config-edit/webhook-config-edit.component';
2626
import { MatSlideToggle, MatSlideToggleModule } from '@angular/material/slide-toggle';
@@ -69,7 +69,7 @@ export class IssuanceConfigCreateComponent implements OnInit {
6969
display: this.fb.array([]),
7070
batchSize: new FormControl(1, [Validators.min(1)]),
7171
dPopRequired: new FormControl(true),
72-
notifyWebhook: fbWebhook,
72+
notifyWebhook: createWebhookFormGroup(),
7373
} as { [k in keyof IssuanceDto]: any });
7474
}
7575

apps/client/src/app/utils/webhook-config-edit/webhook-config-edit.component.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
}
3030
</div>
3131
<div align="end">
32-
<button mat-button type="button" (click)="clearConfig()" [disabled]="group.pristine">
32+
<button mat-button type="button" (click)="clearConfig()">
3333
<mat-icon>clear</mat-icon>
3434
Clear Webhook Configuration
3535
</button>

apps/client/src/app/utils/webhook-config-edit/webhook-config-edit.component.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,18 @@ import { MatSelectModule } from '@angular/material/select';
66
import { FlexLayoutModule } from 'ngx-flexible-layout';
77
import { MatAnchor } from '@angular/material/button';
88

9-
export const fbWebhook = new FormGroup({
10-
url: new FormControl(''),
11-
auth: new FormGroup({
12-
type: new FormControl(''),
13-
config: new FormGroup({
14-
headerName: new FormControl(''),
15-
value: new FormControl(''),
9+
export function createWebhookFormGroup(): FormGroup {
10+
return new FormGroup({
11+
url: new FormControl(''),
12+
auth: new FormGroup({
13+
type: new FormControl(''),
14+
config: new FormGroup({
15+
headerName: new FormControl(''),
16+
value: new FormControl(''),
17+
}),
1618
}),
17-
}),
18-
});
19+
});
20+
}
1921

2022
@Component({
2123
selector: 'app-webhook-config-edit',

packages/eudiplo-sdk/src/api/schemas.gen.ts

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -994,6 +994,53 @@ export const OfferRequestDtoSchema = {
994994
],
995995
description: "The type of response expected for the offer request.",
996996
},
997+
credentialClaims: {
998+
type: "object",
999+
description:
1000+
"Credential claims configuration per credential. Keys must match credentialConfigurationIds.",
1001+
properties: {
1002+
additionalProperties: {
1003+
oneOf: [
1004+
{
1005+
type: "object",
1006+
properties: {
1007+
type: {
1008+
type: "string",
1009+
enum: ["inline"],
1010+
},
1011+
claims: {
1012+
type: "object",
1013+
additionalProperties: true,
1014+
},
1015+
},
1016+
required: ["type", "claims"],
1017+
},
1018+
{
1019+
type: "object",
1020+
properties: {
1021+
type: {
1022+
type: "string",
1023+
enum: ["webhook"],
1024+
},
1025+
webhook: {
1026+
type: "object",
1027+
},
1028+
},
1029+
required: ["type", "webhook"],
1030+
},
1031+
],
1032+
},
1033+
},
1034+
example: {
1035+
citizen: {
1036+
type: "inline",
1037+
claims: {
1038+
given_name: "John",
1039+
family_name: "Doe",
1040+
},
1041+
},
1042+
},
1043+
},
9971044
flow: {
9981045
description: "The flow type for the offer request.",
9991046
enum: ["authorization_code", "pre_authorized_code"],
@@ -1011,11 +1058,6 @@ export const OfferRequestDtoSchema = {
10111058
type: "string",
10121059
},
10131060
},
1014-
credentialClaims: {
1015-
type: "object",
1016-
description:
1017-
"Credential claims configuration per credential.\nEach credential can have claims provided inline or fetched via webhook.\nKeys must be a subset of credentialConfigurationIds.",
1018-
},
10191061
notifyWebhook: {
10201062
description:
10211063
"Webhook to notify about the status of the issuance process.",

packages/eudiplo-sdk/src/api/types.gen.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -585,6 +585,24 @@ export type OfferRequestDto = {
585585
* The type of response expected for the offer request.
586586
*/
587587
response_type: "qrcode" | "uri" | "dc-api";
588+
/**
589+
* Credential claims configuration per credential. Keys must match credentialConfigurationIds.
590+
*/
591+
credentialClaims?: {
592+
additionalProperties?:
593+
| {
594+
type: "inline";
595+
claims: {
596+
[key: string]: unknown;
597+
};
598+
}
599+
| {
600+
type: "webhook";
601+
webhook: {
602+
[key: string]: unknown;
603+
};
604+
};
605+
};
588606
/**
589607
* The flow type for the offer request.
590608
*/
@@ -597,14 +615,6 @@ export type OfferRequestDto = {
597615
* List of credential configuration ids to be included in the offer.
598616
*/
599617
credentialConfigurationIds: Array<string>;
600-
/**
601-
* Credential claims configuration per credential.
602-
* Each credential can have claims provided inline or fetched via webhook.
603-
* Keys must be a subset of credentialConfigurationIds.
604-
*/
605-
credentialClaims?: {
606-
[key: string]: unknown;
607-
};
608618
/**
609619
* Webhook to notify about the status of the issuance process.
610620
*/

0 commit comments

Comments
 (0)