-
Notifications
You must be signed in to change notification settings - Fork 10
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #181 from AliMD/feat/token
New package: @alwatr/token
- Loading branch information
Showing
10 changed files
with
331 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import {AlwatrTokenGenerator} from '@alwatr/token'; | ||
|
||
import type {DigestAlgorithm} from '@alwatr/token'; | ||
|
||
|
||
if (process.env.NODE_ENV !== 'production') { | ||
console.log('Please run node in production for benchmark. NODE_ENV=production node demo/token/benchmark.js'); | ||
process.exit(); | ||
} | ||
|
||
const tokenGenerator = new AlwatrTokenGenerator({ | ||
secret: 'my-very-secret-key', | ||
duration: '1h', | ||
algorithm: 'sha512', | ||
encoding: 'base64', | ||
}); | ||
|
||
const sampleData = 'Lorem ipsum dolor sit amet consectetur adipisicing elit.'; | ||
|
||
function benchmark(algorithm: DigestAlgorithm): void { | ||
tokenGenerator.config.algorithm = algorithm; | ||
const now = Date.now(); | ||
const testRun = 1_000_000; | ||
let i = testRun; | ||
for (; i > 0; i--) { | ||
tokenGenerator.generate(sampleData); | ||
} | ||
const runPerSec = Math.round(testRun / (Date.now() - now) * 1000); | ||
console.log(`Benchmark for ${algorithm} runs %s per sec`, runPerSec); | ||
} | ||
|
||
benchmark('md5'); | ||
benchmark('sha1'); | ||
benchmark('sha224'); | ||
benchmark('sha256'); | ||
benchmark('sha384'); | ||
benchmark('sha512'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
import {createLogger} from '@alwatr/logger'; | ||
import {AlwatrTokenGenerator} from '@alwatr/token'; | ||
|
||
import type {TokenStatus} from '@alwatr/token'; | ||
|
||
const logger = createLogger('token/demo'); | ||
|
||
const tokenGenerator = new AlwatrTokenGenerator({ | ||
secret: 'my-very-secret-key', | ||
duration: '2s', | ||
algorithm: 'sha512', | ||
encoding: 'base64url', | ||
}); | ||
|
||
type User = { | ||
id: string; | ||
name: string; | ||
role: 'admin' | 'user'; | ||
auth: string; | ||
}; | ||
|
||
const user: User = { | ||
id: 'alimd', | ||
name: 'Ali Mihandoost', | ||
role: 'admin', | ||
auth: '', // Generated in first login | ||
}; | ||
|
||
// ------ | ||
|
||
// For example when user authenticated we send user data contain valid auth token. | ||
function login(): User { | ||
user.auth = tokenGenerator.generate(`${user.id}-${user.role}`); | ||
logger.logMethodFull('login', {}, {user}); | ||
return user; | ||
} | ||
|
||
// Now request received and we want to validate the token to ensure that the user is authenticated. | ||
function userValidate(user: User): TokenStatus { | ||
const validateStatus = tokenGenerator.verify(`${user.id}-${user.role}`, user.auth); | ||
logger.logMethodFull('userValidate', {user}, {validateStatus}); | ||
return validateStatus; | ||
} | ||
|
||
|
||
// demo | ||
const userData = login(); | ||
userValidate(userData); // { validateStatus: 'valid' } | ||
|
||
setTimeout(() => { | ||
// 2s later | ||
userValidate(user); // { validateStatus: 'expired' } | ||
}, 2001); | ||
|
||
setTimeout(() => { | ||
// 4s later | ||
userValidate(user); | ||
}, 4001); // { validateStatus: 'invalid' } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
# @alwatr/token | ||
|
||
Secure authentication HOTP token generator (HMAC-based One-Time Password algorithm) written in tiny TypeScript module. | ||
|
||
## Example | ||
|
||
```ts | ||
import {createLogger} from '@alwatr/logger'; | ||
import {AlwatrTokenGenerator} from '@alwatr/token'; | ||
|
||
import type {TokenStatus} from '@alwatr/token'; | ||
|
||
type User = { | ||
id: string; | ||
name: string; | ||
role: 'admin' | 'user'; | ||
auth: string; | ||
}; | ||
|
||
const logger = createLogger('token/demo'); | ||
|
||
const tokenGenerator = new AlwatrTokenGenerator({ | ||
secret: 'my-very-secret-key', | ||
duration: '1h', | ||
algorithm: 'sha512', | ||
encoding: 'base64url', | ||
}); | ||
|
||
const user: User = { | ||
id: 'alimd', | ||
name: 'Ali Mihandoost', | ||
role: 'admin', | ||
auth: '', // Generated in first login | ||
}; | ||
|
||
// ------ | ||
|
||
// For example when user authenticated we send user data contain valid auth token. | ||
function login(): User { | ||
user.auth = tokenGenerator.generate(`${user.id}-${user.role}`); | ||
logger.logMethodFull('login', {}, {user}); | ||
return user; | ||
} | ||
|
||
// Now request received and we want to validate the token to ensure that the user is authenticated. | ||
function userValidate(user: User): TokenStatus { | ||
const validateStatus = tokenGenerator.verify(`${user.id}-${user.role}`, user.auth); | ||
logger.logMethodFull('userValidate', {user}, {validateStatus}); | ||
return validateStatus; | ||
} | ||
|
||
// demo | ||
const userData = login(); | ||
userValidate(userData); // 'valid' | ||
|
||
// one hour later | ||
userValidate(user); // 'expired' | ||
|
||
// one hours later | ||
userValidate(user); // 'invalid' | ||
``` | ||
|
||
## References | ||
|
||
- [RFC 4226](http://tools.ietf.org/html/rfc4226). HMAC-Based One-Time Password Algorithm (HOTP) | ||
- [RFC 6238](http://tools.ietf.org/html/rfc6238). Time-Based One-Time Password Algorithm (TOTP) | ||
- [HMAC: Keyed-Hashing for Message Authentication](https://tools.ietf.org/html/rfc2104). (February 1997). Network Working Group. | ||
- [HMAC and Key Derivation](https://cryptobook.nakov.com/mac-and-key-derivation/hmac-and-key-derivation). Practical Cryptography for Developers. | ||
- [HMAC Generator/Tester Tool](https://www.freeformatter.com/hmac-generator.html). FreeFormatter. | ||
- [How API Request Signing Works (And How to Implement HMAC in NodeJS)](https://blog.andrewhoang.me/how-api-request-signing-works-and-how-to-implement-it-in-nodejs-2/). (2016). Andrew Hoang. | ||
- [Implement HMAC Authentication](https://support.google.com/admanager/answer/7637490?hl=en). Google Ad Manager Help. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
{ | ||
"name": "@alwatr/token", | ||
"version": "0.12.0", | ||
"description": "Secure authentication HOTP token generator (the HMAC-based One-Time Password algorithm) written in tiny TypeScript module.", | ||
"keywords": [ | ||
"token", | ||
"authentication", | ||
"auth", | ||
"access", | ||
"token", | ||
"hmac", | ||
"hash", | ||
"time", | ||
"otp", | ||
"hotp", | ||
"otp-token", | ||
"typescript", | ||
"esm", | ||
"alwatr" | ||
], | ||
"main": "token.js", | ||
"type": "module", | ||
"types": "token.d.ts", | ||
"author": "S. Ali Mihandoost <ali.mihandoost@gmail.com>", | ||
"contributors": [], | ||
"license": "MIT", | ||
"files": [ | ||
"**/*.{d.ts.map,d.ts,js.map,js,html,md}" | ||
], | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/AliMD/alwatr", | ||
"directory": "packages/core/token" | ||
}, | ||
"homepage": "https://github.com/AliMD/alwatr/tree/main/packages/core/token#readme", | ||
"bugs": { | ||
"url": "https://github.com/AliMD/alwatr/issues" | ||
}, | ||
"dependencies": { | ||
"tslib": "^2.3.1", | ||
"@alwatr/logger": "^0.12.0", | ||
"@alwatr/math": "^0.12.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import {createHmac} from 'node:crypto'; | ||
|
||
import {alwatrRegisteredList, createLogger} from '@alwatr/logger'; | ||
import {parseDuration} from '@alwatr/math'; | ||
|
||
import type {TokenGeneratorConfig, TokenStatus} from './type.js'; | ||
|
||
export * from './type.js'; | ||
|
||
alwatrRegisteredList.push({ | ||
name: '@alwatr/token', | ||
version: '{{ALWATR_VERSION}}', | ||
}); | ||
|
||
export class AlwatrTokenGenerator { | ||
protected _logger = createLogger('alwatr-token-generator'); | ||
private _duration: number; | ||
|
||
get epoch(): number { | ||
return Math.floor(Date.now() / this._duration); | ||
} | ||
|
||
constructor(public config: TokenGeneratorConfig) { | ||
this._logger.logMethodArgs('constructor', config); | ||
this._duration = parseDuration(config.duration); | ||
} | ||
|
||
protected _generate(data: string, epoch: number): string { | ||
return createHmac(this.config.algorithm, data) | ||
.update(data + epoch) | ||
.digest(this.config.encoding); | ||
} | ||
|
||
generate(data: string): string { | ||
return this._generate(data, this.epoch); | ||
} | ||
|
||
verify(data: string, token: string): TokenStatus { | ||
const epoch = this.epoch; | ||
if (token === this._generate(data, epoch)) { | ||
return 'valid'; | ||
} else if (token === this._generate(data, epoch - 1)) { | ||
return 'expired'; | ||
} else { | ||
return 'invalid'; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import type {DurationString} from '@alwatr/math'; | ||
|
||
export type DigestAlgorithm = 'md5' | 'sha1' | 'sha224' | 'sha256' | 'sha384' | 'sha512'; | ||
|
||
export type TokenStatus = 'valid' | 'invalid' | 'expired'; | ||
|
||
export type TokenGeneratorConfig = { | ||
/** | ||
* Secret string data to generate token. | ||
*/ | ||
secret: string; | ||
|
||
/** | ||
* Token expiration time. | ||
*/ | ||
duration: DurationString; | ||
|
||
/** | ||
* OpenSSl digest algorithm. | ||
*/ | ||
algorithm: DigestAlgorithm; | ||
|
||
/** | ||
* Encoding of token. | ||
*/ | ||
encoding: 'base64' | 'base64url' | 'hex' | 'binary'; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{ | ||
"extends": "../../../tsconfig.json", | ||
"compilerOptions": { | ||
"composite": true, | ||
"tsBuildInfoFile": ".tsbuildinfo", | ||
"rootDir": "src", | ||
"outDir": "." | ||
}, | ||
// files, include and exclude from the inheriting config are always overwritten. | ||
"include": ["src/**/*"], | ||
"exclude": [], | ||
"references": [ | ||
{ "path": "../logger" }, | ||
{ "path": "../math" }, | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters