@haus-tech/bankid-auth-plugin 1.0.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.
@@ -0,0 +1,19 @@
1
+ import { BankIdAuthService } from './services/bankid-auth.service';
2
+ import { CacheService, RequestContext } from '@vendure/core';
3
+ export declare class BankIdAuthController {
4
+ private bankIdAuthService;
5
+ private cacheService;
6
+ constructor(bankIdAuthService: BankIdAuthService, cacheService: CacheService);
7
+ initiate(ctx: RequestContext): Promise<{
8
+ data: import("./types").InitiateResponse;
9
+ }>;
10
+ authenticate(ctx: RequestContext, body: {
11
+ orderRef: string;
12
+ isSameDevice: boolean;
13
+ }): Promise<{
14
+ data: import("./types").AuthResponse;
15
+ }>;
16
+ cancel(ctx: RequestContext, body: {
17
+ orderRef: string;
18
+ }): Promise<void>;
19
+ }
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.BankIdAuthController = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const bankid_auth_service_1 = require("./services/bankid-auth.service");
18
+ const core_1 = require("@vendure/core");
19
+ const utils_1 = require("./utils");
20
+ const utils_2 = require("./utils");
21
+ let BankIdAuthController = class BankIdAuthController {
22
+ bankIdAuthService;
23
+ cacheService;
24
+ constructor(bankIdAuthService, cacheService) {
25
+ this.bankIdAuthService = bankIdAuthService;
26
+ this.cacheService = cacheService;
27
+ }
28
+ async initiate(ctx) {
29
+ const endUserIp = (0, utils_1.getEndUserIp)(ctx);
30
+ try {
31
+ const result = await this.bankIdAuthService.initiate(ctx, endUserIp);
32
+ const cacheKey = `bankid:session:${result.orderRef}`;
33
+ this.cacheService.set(cacheKey, endUserIp, { ttl: 120000 });
34
+ return { data: result };
35
+ }
36
+ catch (error) {
37
+ throw (0, utils_1.handleBankIdError)(error);
38
+ }
39
+ }
40
+ async authenticate(ctx, body) {
41
+ if (!body.orderRef) {
42
+ throw new common_1.HttpException('orderRef is required', common_1.HttpStatus.BAD_REQUEST);
43
+ }
44
+ const cacheKey = `bankid:session:${body.orderRef}`;
45
+ const cachedIpAddress = (await this.cacheService.get(cacheKey));
46
+ try {
47
+ const res = await this.bankIdAuthService.authenticate(ctx, body.orderRef, cachedIpAddress, body.isSameDevice);
48
+ return { data: res };
49
+ }
50
+ catch (error) {
51
+ if (error instanceof utils_2.IpMismatchError) {
52
+ throw new common_1.UnauthorizedException(error);
53
+ }
54
+ throw (0, utils_1.handleBankIdError)(error);
55
+ }
56
+ }
57
+ async cancel(ctx, body) {
58
+ if (!body.orderRef) {
59
+ throw new common_1.HttpException('orderRef is required', common_1.HttpStatus.BAD_REQUEST);
60
+ }
61
+ try {
62
+ await this.bankIdAuthService.cancel(ctx, body.orderRef);
63
+ }
64
+ catch (error) {
65
+ throw (0, utils_1.handleBankIdError)(error);
66
+ }
67
+ }
68
+ };
69
+ __decorate([
70
+ (0, common_1.Post)('initiate'),
71
+ (0, common_1.HttpCode)(common_1.HttpStatus.CREATED),
72
+ __param(0, (0, core_1.Ctx)()),
73
+ __metadata("design:type", Function),
74
+ __metadata("design:paramtypes", [core_1.RequestContext]),
75
+ __metadata("design:returntype", Promise)
76
+ ], BankIdAuthController.prototype, "initiate", null);
77
+ __decorate([
78
+ (0, common_1.Post)('authenticate'),
79
+ (0, common_1.HttpCode)(common_1.HttpStatus.OK),
80
+ __param(0, (0, core_1.Ctx)()),
81
+ __param(1, (0, common_1.Body)()),
82
+ __metadata("design:type", Function),
83
+ __metadata("design:paramtypes", [core_1.RequestContext, Object]),
84
+ __metadata("design:returntype", Promise)
85
+ ], BankIdAuthController.prototype, "authenticate", null);
86
+ __decorate([
87
+ (0, common_1.Post)('cancel'),
88
+ (0, common_1.HttpCode)(common_1.HttpStatus.OK),
89
+ __param(0, (0, core_1.Ctx)()),
90
+ __param(1, (0, common_1.Body)()),
91
+ __metadata("design:type", Function),
92
+ __metadata("design:paramtypes", [core_1.RequestContext, Object]),
93
+ __metadata("design:returntype", Promise)
94
+ ], BankIdAuthController.prototype, "cancel", null);
95
+ BankIdAuthController = __decorate([
96
+ (0, common_1.Controller)('api/bankid'),
97
+ __metadata("design:paramtypes", [bankid_auth_service_1.BankIdAuthService,
98
+ core_1.CacheService])
99
+ ], BankIdAuthController);
100
+ exports.BankIdAuthController = BankIdAuthController;
@@ -0,0 +1,6 @@
1
+ import { Type } from '@vendure/core';
2
+ import { BankIdAuthPluginOptions } from './types';
3
+ export declare class BankIdAuthPlugin {
4
+ static options: BankIdAuthPluginOptions;
5
+ static init(options: BankIdAuthPluginOptions): Type<BankIdAuthPlugin>;
6
+ }
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var BankIdAuthPlugin_1;
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.BankIdAuthPlugin = void 0;
11
+ const core_1 = require("@vendure/core");
12
+ const constants_1 = require("./constants");
13
+ const bankid_auth_service_1 = require("./services/bankid-auth.service");
14
+ const bankid_auth_controller_1 = require("./bankid-auth.controller");
15
+ let BankIdAuthPlugin = BankIdAuthPlugin_1 = class BankIdAuthPlugin {
16
+ static options;
17
+ static init(options) {
18
+ this.options = options;
19
+ if (!options.postAuthStrategy) {
20
+ throw new Error('BankIdAuthPlugin requires a PostAuthenticateStrategy but none was provided');
21
+ }
22
+ if (!options.bankIdApiBaseUrl) {
23
+ throw new Error('BankIdAuthPlugin requires a bankIdApiBaseUrl but none was provided');
24
+ }
25
+ return BankIdAuthPlugin_1;
26
+ }
27
+ };
28
+ BankIdAuthPlugin = BankIdAuthPlugin_1 = __decorate([
29
+ (0, core_1.VendurePlugin)({
30
+ imports: [core_1.PluginCommonModule],
31
+ providers: [
32
+ { provide: constants_1.BANKID_AUTH_PLUGIN_OPTIONS, useFactory: () => BankIdAuthPlugin_1.options },
33
+ bankid_auth_service_1.BankIdAuthService,
34
+ ],
35
+ configuration: (config) => {
36
+ // Plugin-specific configuration
37
+ // such as custom fields, custom permissions,
38
+ // strategies etc. can be configured here by
39
+ // modifying the `config` object.
40
+ return config;
41
+ },
42
+ controllers: [bankid_auth_controller_1.BankIdAuthController],
43
+ compatibility: '^3.0.0',
44
+ })
45
+ ], BankIdAuthPlugin);
46
+ exports.BankIdAuthPlugin = BankIdAuthPlugin;
@@ -0,0 +1,2 @@
1
+ export declare const BANKID_AUTH_PLUGIN_OPTIONS: unique symbol;
2
+ export declare const loggerCtx = "BankidAuthPlugin";
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loggerCtx = exports.BANKID_AUTH_PLUGIN_OPTIONS = void 0;
4
+ exports.BANKID_AUTH_PLUGIN_OPTIONS = Symbol('BANKID_AUTH_PLUGIN_OPTIONS');
5
+ exports.loggerCtx = 'BankidAuthPlugin';
@@ -0,0 +1,13 @@
1
+ import { ModuleRef } from '@nestjs/core';
2
+ import { RequestContext } from '@vendure/core';
3
+ import { InitiateResponse, AuthResponse, BankIdAuthPluginOptions } from '../types';
4
+ export declare class BankIdAuthService {
5
+ private options;
6
+ private moduleRef;
7
+ private readonly axiosInstance;
8
+ constructor(options: BankIdAuthPluginOptions, moduleRef: ModuleRef);
9
+ initiate(ctx: RequestContext, endUserIp: string): Promise<InitiateResponse>;
10
+ authenticate(ctx: RequestContext, orderRef: string, cachedIpAddress: string, isSameDevice: boolean): Promise<AuthResponse>;
11
+ cancel(ctx: RequestContext, orderRef: string): Promise<boolean>;
12
+ private collect;
13
+ }
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ var __importDefault = (this && this.__importDefault) || function (mod) {
15
+ return (mod && mod.__esModule) ? mod : { "default": mod };
16
+ };
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.BankIdAuthService = void 0;
19
+ const common_1 = require("@nestjs/common");
20
+ const core_1 = require("@nestjs/core");
21
+ const core_2 = require("@vendure/core");
22
+ const constants_1 = require("../constants");
23
+ const axios_1 = __importDefault(require("axios"));
24
+ const https_1 = __importDefault(require("https"));
25
+ const types_1 = require("../types");
26
+ const utils_1 = require("../utils");
27
+ let BankIdAuthService = class BankIdAuthService {
28
+ options;
29
+ moduleRef;
30
+ axiosInstance;
31
+ constructor(options, moduleRef) {
32
+ this.options = options;
33
+ this.moduleRef = moduleRef;
34
+ this.axiosInstance = axios_1.default.create({
35
+ baseURL: this.options.bankIdApiBaseUrl,
36
+ httpsAgent: new https_1.default.Agent({
37
+ cert: this.options.clientCert,
38
+ key: this.options.privateKey,
39
+ ca: this.options.caCert,
40
+ rejectUnauthorized: false,
41
+ }),
42
+ });
43
+ }
44
+ async initiate(ctx, endUserIp) {
45
+ try {
46
+ const response = await this.axiosInstance.post('/auth', { endUserIp });
47
+ return response.data;
48
+ }
49
+ catch (error) {
50
+ throw error;
51
+ }
52
+ }
53
+ async authenticate(ctx, orderRef, cachedIpAddress, isSameDevice) {
54
+ try {
55
+ const response = await this.collect(ctx, orderRef, 2000);
56
+ if (response.status === types_1.BankIdOrderStatus.Failed) {
57
+ return { success: false, hintCode: response?.hintCode };
58
+ }
59
+ if (response.status === types_1.BankIdOrderStatus.Complete && response.completionData != null) {
60
+ const { user, device } = response.completionData;
61
+ if (isSameDevice && cachedIpAddress != device.ipAddress) {
62
+ throw new utils_1.IpMismatchError(cachedIpAddress, device.ipAddress);
63
+ }
64
+ const { userId } = await this.options.postAuthStrategy.handleSuccess(ctx, new core_2.Injector(this.moduleRef), {
65
+ personalNumber: user.personalNumber,
66
+ });
67
+ return {
68
+ success: true,
69
+ userId: userId,
70
+ };
71
+ }
72
+ }
73
+ catch (error) {
74
+ throw error;
75
+ }
76
+ return { success: false };
77
+ }
78
+ async cancel(ctx, orderRef) {
79
+ try {
80
+ const response = await this.axiosInstance.post('/cancel', { orderRef });
81
+ if (response.status !== 200) {
82
+ throw new Error('Failed to cancel order');
83
+ }
84
+ return true;
85
+ }
86
+ catch (error) {
87
+ throw error;
88
+ }
89
+ }
90
+ async collect(ctx, orderRef, pollingIntervalMs) {
91
+ return new Promise((resolve, reject) => {
92
+ const interval = setInterval(async () => {
93
+ try {
94
+ const response = await this.axiosInstance.post('/collect', { orderRef });
95
+ if (response.data.status === types_1.BankIdOrderStatus.Complete) {
96
+ clearInterval(interval);
97
+ resolve(response.data);
98
+ }
99
+ if (response.data.status === types_1.BankIdOrderStatus.Failed) {
100
+ clearInterval(interval);
101
+ reject(response.data);
102
+ }
103
+ }
104
+ catch (error) {
105
+ clearInterval(interval);
106
+ reject(error);
107
+ }
108
+ }, pollingIntervalMs);
109
+ });
110
+ }
111
+ };
112
+ BankIdAuthService = __decorate([
113
+ (0, common_1.Injectable)(),
114
+ __param(0, (0, common_1.Inject)(constants_1.BANKID_AUTH_PLUGIN_OPTIONS)),
115
+ __metadata("design:paramtypes", [Object, core_1.ModuleRef])
116
+ ], BankIdAuthService);
117
+ exports.BankIdAuthService = BankIdAuthService;
@@ -0,0 +1,58 @@
1
+ import { InjectableStrategy, Injector, RequestContext } from '@vendure/core';
2
+ /**
3
+ * @description
4
+ * The plugin can be configured using the following options:
5
+ */
6
+ export interface BankIdAuthPluginOptions {
7
+ bankIdApiBaseUrl: string;
8
+ clientCert: string;
9
+ privateKey: string;
10
+ caCert: string;
11
+ postAuthStrategy: PostAuthenticateStrategy;
12
+ }
13
+ export interface InitiateResponse {
14
+ orderRef: string;
15
+ autoStartToken: string;
16
+ qrStartToken: string;
17
+ qrStartSecret: string;
18
+ }
19
+ export interface CollectResponse {
20
+ orderRef: BankIdOrderStatus.Complete | BankIdOrderStatus.Failed | BankIdOrderStatus.Pending;
21
+ status: string;
22
+ hintCode: string;
23
+ completionData?: CompletionData;
24
+ }
25
+ export interface CompletionData {
26
+ bankIdIssueDate: string;
27
+ user: User;
28
+ device: Device;
29
+ }
30
+ export interface Device {
31
+ ipAddress: string;
32
+ }
33
+ export interface User {
34
+ personalNumber: string;
35
+ name: string;
36
+ }
37
+ export interface AuthResponse {
38
+ success: boolean;
39
+ userId?: string;
40
+ hintCode?: string;
41
+ }
42
+ export declare enum BankIdOrderStatus {
43
+ Pending = "pending",
44
+ Complete = "complete",
45
+ Failed = "failed"
46
+ }
47
+ export interface BadRequestResponse {
48
+ errorCode: string;
49
+ details: string;
50
+ }
51
+ export interface PostAuthenticateStrategy extends InjectableStrategy {
52
+ handleSuccess(ctx: RequestContext, injector: Injector, input: {
53
+ personalNumber: string;
54
+ }): Promise<AuthenticatedUser>;
55
+ }
56
+ export interface AuthenticatedUser {
57
+ userId: string;
58
+ }
package/dist/types.js ADDED
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BankIdOrderStatus = void 0;
4
+ var BankIdOrderStatus;
5
+ (function (BankIdOrderStatus) {
6
+ BankIdOrderStatus["Pending"] = "pending";
7
+ BankIdOrderStatus["Complete"] = "complete";
8
+ BankIdOrderStatus["Failed"] = "failed";
9
+ })(BankIdOrderStatus = exports.BankIdOrderStatus || (exports.BankIdOrderStatus = {}));
@@ -0,0 +1,9 @@
1
+ import { HttpException } from '@nestjs/common';
2
+ import { RequestContext } from '@vendure/core';
3
+ export declare function handleBankIdError(error: any): HttpException;
4
+ export declare function getEndUserIp(ctx: RequestContext): string;
5
+ export declare class IpMismatchError extends Error {
6
+ expected: string;
7
+ actual: string;
8
+ constructor(expected: string, actual: string);
9
+ }
package/dist/utils.js ADDED
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.IpMismatchError = exports.getEndUserIp = exports.handleBankIdError = void 0;
4
+ const common_1 = require("@nestjs/common");
5
+ function handleBankIdError(error) {
6
+ if (error?.isAxiosError) {
7
+ const axiosError = error;
8
+ const status = axiosError.response?.status || 500;
9
+ if (status >= 500) {
10
+ return new common_1.HttpException({
11
+ message: 'BankID service unavailable',
12
+ details: axiosError.response?.data,
13
+ }, status === common_1.HttpStatus.INTERNAL_SERVER_ERROR ? common_1.HttpStatus.BAD_GATEWAY : status);
14
+ }
15
+ if (status === 400) {
16
+ return new common_1.HttpException({
17
+ message: 'Invalid request to BankID',
18
+ }, status);
19
+ }
20
+ }
21
+ return new common_1.HttpException({
22
+ message: 'Unexpected error',
23
+ }, common_1.HttpStatus.INTERNAL_SERVER_ERROR);
24
+ }
25
+ exports.handleBankIdError = handleBankIdError;
26
+ function getEndUserIp(ctx) {
27
+ const xForwardedFor = ctx.req?.headers['x-forwarded-for'];
28
+ if (typeof xForwardedFor === 'string') {
29
+ return xForwardedFor.split(',')[0].trim();
30
+ }
31
+ if (Array.isArray(xForwardedFor)) {
32
+ return xForwardedFor[0];
33
+ }
34
+ throw new Error('Could not determine end user IP');
35
+ }
36
+ exports.getEndUserIp = getEndUserIp;
37
+ class IpMismatchError extends Error {
38
+ expected;
39
+ actual;
40
+ constructor(expected, actual) {
41
+ super(`IP mismatch: expected ${expected}, got ${actual}`);
42
+ this.expected = expected;
43
+ this.actual = actual;
44
+ this.expected = expected;
45
+ this.actual = actual;
46
+ this.name = 'IpMismatchError';
47
+ Object.setPrototypeOf(this, IpMismatchError.prototype);
48
+ }
49
+ }
50
+ exports.IpMismatchError = IpMismatchError;
package/package.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "name": "@haus-tech/bankid-auth-plugin",
3
+ "version": "1.0.0",
4
+ "description": "Support for authentication via Swedish BankID",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "README.md"
10
+ ],
11
+ "scripts": {
12
+ "start": "yarn ts-node test/dev-server.ts",
13
+ "build": "yarn run -T rimraf dist && yarn run -T tsc && yarn run -T copyfiles -u 1 'src/ui/**/*' dist/",
14
+ "test": "jest --preset=\"ts-jest\"",
15
+ "prepublishOnly": "yarn && yarn build"
16
+ }
17
+ }