Skip to content

Commit

Permalink
Merge pull request #19 from DSorlov/dev
Browse files Browse the repository at this point in the history
v1.0.0 final
  • Loading branch information
DSorlov authored Jun 5, 2021
2 parents fc143b7 + ff409d3 commit 0510774
Show file tree
Hide file tree
Showing 67 changed files with 2,662 additions and 3,672 deletions.
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2020 Daniel Sörlöv
Copyright (c) 2020-2021 Daniel Sörlöv

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
28 changes: 0 additions & 28 deletions certs/frejaeid_prod_onjnxVgI3oUzWQMLciD7sQZ4mqM.jwt

This file was deleted.

24 changes: 0 additions & 24 deletions certs/frejaeid_test_HwMHK_gb3_iuNF1advMtlG0-fUs.jwt

This file was deleted.

18 changes: 16 additions & 2 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
# Changelog for eid-provider
# Changelog for eid

The format is based on [Keep a Changelog][keep-a-changelog]
<!-- and this project adheres to [Semantic Versioning][semantic-versioning]. -->

## [Unreleased]
- Nothing right now

## [1.0.0] (2021-06-05)

### Breaking changes
- Package name changed from eid-provider to eid
- Major rehauling of architechture and internal file locations
- Completely new interface for all operations
- Compability calls implemented to make transition easier to eid
- frejaeid and frejaorgid have been merged into one client frejaeid
- ftbankid and ftfrejaeid have been merged into new client grp2
- gbankid, bfrejaeid and ghsaid have been merged into new client grandid
- Implemented new client signicat
- Reduced external dependencies, now only jsonwebtoken is required.
- Implemented support for QR-codes (API v5.1) in bankid

## [0.2.1] (2021-04-08)

Expand Down Expand Up @@ -104,6 +117,7 @@ The format is based on [Keep a Changelog][keep-a-changelog]

[keep-a-changelog]: http://keepachangelog.com/en/1.0.0/
[Unreleased]: https://github.com/DSorlov/eid-provider/compare/master...dev
[1.0.0]: https://github.com/DSorlov/eid-provider/releases/tag/v1.0.0
[0.2.1]: https://github.com/DSorlov/eid-provider/releases/tag/v0.2.1
[0.2.0]: https://github.com/DSorlov/eid-provider/releases/tag/v0.2.0
[0.1.9]: https://github.com/DSorlov/eid-provider/releases/tag/v0.1.9
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
214 changes: 214 additions & 0 deletions clients/bankid/client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
const BaseClient = require('../baseclient.js');
var crypto = require('crypto');

class BankID extends BaseClient {


constructor(settings) {
super(settings);
this.settings = settings || {};

this.clientInfo = {
name: "BankID",
version: "20210406",
author: "Daniel Sörlöv <[email protected]>",
url: "https://github.com/DSorlov/eid-provider",
methods: ['auth','sign']
};

this._customAgent({
pfx: this.settings.client_cert,
passphrase: this.settings.password,
ca: this.settings.ca_cert
});

};

async pollRequest(data) {
if (typeof data !== 'object') return this._createErrorMessage('internal_error','Supplied argument is not a class');
if (!data.id || typeof data.id !== 'string') return this._createErrorMessage('internal_error','Id argument must be string');

var postData = {
orderRef: data.id
};
var result = await this._httpRequest(`${this.settings.endpoint}/collect`,{},JSON.stringify(postData));

if (result.statusCode===599) {
return this._createErrorMessage('internal_error',result.statusMessage);
} else if (result.statusCode===200) {

if (result.json.hintCode) {
switch(result.json.hintCode) {
case "expiredTransaction":
return this._createErrorMessage('expired_transaction');
case "outstandingTransaction":
return this._createPendingMessage('notdelivered');
case "userSign":
return this._createPendingMessage('user_in_app');
case "noClient":
return this._createPendingMessage('delivered');
case "userCancel":
return this._createErrorMessage('cancelled_by_user');
case "cancelled":
return this._createErrorMessage('cancelled_by_idp');
default:
return this._createErrorMessage('api_error', `Unknwon error '${result.json.hintCode}' was received`);
}
} else {

if (result.json.status==="complete") {

return this._createCompletionMessage(
result.json.completionData.user.personalNumber,
result.json.completionData.user.givenName,
result.json.completionData.user.surname,
result.json.completionData.user.name, {
signature: result.json.completionData.signature,
ocspResponse: result.json.completionData.ocspResponse
});

} else {
return this._createErrorMessage('communication_error', result.data);
}

}

} else {
if (result.json.errorCode) {
switch(result.json.errorCode) {
case 'invalidParameters':
return this._createErrorMessage('request_id_invalid');
default:
return this._createErrorMessage('api_error', `Unknwon error '${result.json.errorCode}' was received`);
}
} else {
return this._createErrorMessage('communication_error',result.statusMessage);
}
}

}

async cancelRequest(data) {
if (typeof data !== 'object') return this._createErrorMessage('internal_error','Supplied argument is not a class');
if (!data.id || typeof data.id !== 'string') return this._createErrorMessage('internal_error','Id argument must be string');

var postData = {
orderRef: data.id
};
var result = await this._httpRequest(`${this.settings.endpoint}/cancel`,{},JSON.stringify(postData));

if (result.statusCode===599) {
return this._createErrorMessage('internal_error',result.statusMessage);
} else if (result.statusCode===200) {
return this._createSuccessMessage();
} else {
return this._createErrorMessage('communication_error',result.statusMessage);
}

}

createQRCodeString(data) {
if (typeof data !== 'object') return this._createErrorMessage('internal_error','Supplied argument is not a class');
if (!data.qrStartSecret||!data.qrStartToken||!data.qrAuthTime) this._createErrorMessage('internal_error','Needed attributes not supplied');
var initTime = (new Date(data.qrAuthTime)).getTime();
var currTime = (new Date()).getTime();
var timeDiff = Math.floor((currTime - initTime) / 1000);
var hash = crypto.createHmac('SHA256', data.qrStartSecret).update(timeDiff.toString()).digest("hex");
return `bankid.${data.qrStartToken}.${timeDiff}.${hash}`;
}

async initRequest(data) {
if (typeof data !== 'object') return this._createErrorMessage('internal_error','Supplied argument is not a class');
if (!data.id) return this._createErrorMessage('internal_error','Id argument must be string');
var postData = '';
var endpointUri = '';

if (data.text) {
endpointUri = 'sign';
postData = {
endUserIp: data.endUserIp ? data.endUserUp : '127.0.0.1',
userVisibleData: Buffer.from(data.text).toString('base64'),
requirement: {
allowFingerprint: data.allowFingerprint ? data.allowFingerprint : this.settings.allowFingerprint
}};
} else {
endpointUri = 'auth';
postData = {
endUserIp: data.endUserIp ? data.endUserUp : '127.0.0.1',
requirement: {
allowFingerprint: data.allowFingerprint ? data.allowFingerprint : this.settings.allowFingerprint
}};
}

var personalId = this._unPack(data.id);
if (personalId) postData.personalNumber = personalId;

var result = await this._httpRequest(`${this.settings.endpoint}/${endpointUri}`,{},JSON.stringify(postData));

if (result.statusCode===599) {

return this._createErrorMessage('internal_error',result.statusMessage);

} else if (result.statusCode===200) {

if (result.json.qrStartSecret) {
return this._createInitializationMessage(result.json.orderRef, {
autostart_token: result.json.autoStartToken,
autostart_url: "bankid:///?autostarttoken="+result.json.autoStartToken+"&redirect=null",
qrStartSecret: result.json.qrStartSecret,
qrStartToken: result.json.qrStartToken,
qrAuthTime: Date(),
qrCodeString: createQRCodeString
});
} else {
return this._createInitializationMessage(result.json.orderRef, {
autostart_token: result.json.autoStartToken,
autostart_url: "bankid:///?autostarttoken="+result.json.autoStartToken+"&redirect=null"
});
}


} else {

if (result.json.errorCode) {
switch(result.json.errorCode) {
case 'invalidParameters':
if (result.json.details==='Incorrect personalNumber') {
return this._createErrorMessage('request_ssn_invalid');
} else if (result.json.details==='Invalid userVisibleData') {
return this._createErrorMessage('request_text_invalid');
} else {
return this._createErrorMessage('api_error', `Unknwon parameter error '${result.json.details}' was received`);
}
case 'alreadyInProgress':
return this._createErrorMessage('already_in_progress');
default:
return this._createErrorMessage('api_error', `Unknwon error '${result.json.errorCode}' was received`);
}
} else {
return this._createErrorMessage('communication_error',result.statusMessage);
}

};

}

// Supporting function to take care of any and all types of id that could be sent into bankid
_unPack(data) {
if (data==="") return "";
if (data==="INFERRED") return "";

if (typeof data === 'string') {
return data;
} else {
if (data.ssn) {
return data.ssn;
} else {
return data.toString();
}
}
}

}

module.exports = BankID;
61 changes: 61 additions & 0 deletions clients/bankid/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
## Swedish BankID (BankID)

Client for direct API communication with BankID (Finansiell ID-Teknik AB).



| Information | |
| --- | --- |
| Version | 20210406 |
| Status | Built-in |
| Author | Daniel Sörlöv <[email protected]> |
| Client URL | https://github.com/DSorlov/eid-provider |

### Feature Table

| Feature | Supported |
| --- | --- |
| Authentication | :heavy_check_mark: |
| Signatures | :heavy_check_mark: |

### Configuration Factory

Supports configuration factory using attribute `enviroment` to specify either `production` or `testing`.

```javascript
var config = eid.configFactory({
clientType: 'bankid',
enviroment: 'testing'
});
```

### Configuration Object

Use the Configuration Factory to get a pre-populated object

```javascript
var config = {
// Client type (must be bankid to use this client)
clientType: 'bankid',
// The base URI to call the Rest-API
endpoint: 'https://appapi2.test.bankid.com/rp/v5',
// The PFX file content to use
client_cert: '...',
// The password for the PFX
password: 'test',
// The CA public cert for SSL communications
ca_cert: '...',
// Allow usage of fingerprint to sign in app for end-users
allowFingerprint: true
};
```

### Extension Methods

The `doRequest` and `initRequest` accepts additional parameter `endUserIP` which can be set to the end user ip / remote requester ip. If not supplied it will be replaced by '127.0.0.1' as in earlier versions. Also accept `allowFingerprint` as boolean to specify if fingerprint auth is allowed in the app or not, if not specified default value from config will be used.

If `id` is not supplied to `doRequest` and `initRequest` the request will start and the properties `qrStartSecret`,`qrStartToken`,`qrAuthTime` will be returned as extra attributes for use with QR-code logins. Also the `qrCodeString` is populated with an initial calculation for the request.

**Extension methods**

* `createQRCodeString({qrStartSecret,qrStartToken,qrAuthTime})`<br/>Returns a correctly formatted QR-code for starting BankID app. The paramets are obtained when starting a authentication request without a id. It then returns `qrStartSecret`,`qrStartToken`,`qrAuthTime` as extra attributes. This method must be polled every 5 seconds at the most to obtain a new code when using QR-login.
Loading

0 comments on commit 0510774

Please sign in to comment.