auth-vir 0.0.4 → 1.1.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.
- package/README.md +1 -6
- package/dist/auth.d.ts +13 -3
- package/dist/auth.js +16 -4
- package/dist/cookie.d.ts +16 -2
- package/dist/cookie.js +48 -10
- package/dist/hash.d.ts +18 -11
- package/dist/hash.js +25 -26
- package/package.json +2 -1
- package/src/auth.ts +21 -3
- package/src/cookie.ts +63 -11
- package/src/hash.ts +43 -27
package/README.md
CHANGED
|
@@ -24,12 +24,7 @@ npm i auth-vir
|
|
|
24
24
|
/** When a user creates or resets their password, hash it before storing it in your database. */
|
|
25
25
|
|
|
26
26
|
const hashedPassword = await hashPassword('user input password');
|
|
27
|
-
|
|
28
|
-
if (!hashedPassword) {
|
|
29
|
-
/** This happens if the user password is too long for the bcrypt algorithm. */
|
|
30
|
-
throw new Error('Password too long.');
|
|
31
|
-
}
|
|
32
|
-
/** Now store `hashedPassword` in your database. */
|
|
27
|
+
/** Store `hashedPassword` in your database. */
|
|
33
28
|
```
|
|
34
29
|
|
|
35
30
|
- Compare a stored password hash for login checking:
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type CookieParams } from './cookie.js';
|
|
1
|
+
import { clearAuthCookie, type CookieParams } from './cookie.js';
|
|
2
2
|
import { type ParseJwtParams } from './jwt.js';
|
|
3
3
|
/**
|
|
4
4
|
* All possible headers container types supported by {@link extractUserIdFromRequestHeaders}.
|
|
@@ -20,12 +20,22 @@ export declare function extractUserIdFromRequestHeaders(headers: HeaderContainer
|
|
|
20
20
|
*
|
|
21
21
|
* @category Auth : Host
|
|
22
22
|
*/
|
|
23
|
-
export declare function generateSuccessfulLoginHeaders(
|
|
23
|
+
export declare function generateSuccessfulLoginHeaders(
|
|
24
24
|
/** The id from your database of the user you're authenticating. */
|
|
25
|
-
cookieConfig: Readonly<CookieParams>): Promise<{
|
|
25
|
+
userId: string, cookieConfig: Readonly<CookieParams>): Promise<{
|
|
26
26
|
'set-cookie': string;
|
|
27
27
|
"csrf-token": string;
|
|
28
28
|
}>;
|
|
29
|
+
/**
|
|
30
|
+
* Used by host (backend) code to set headers on a response object when the user has logged out or
|
|
31
|
+
* failed to authorize.
|
|
32
|
+
*
|
|
33
|
+
* @category Auth : Host
|
|
34
|
+
*/
|
|
35
|
+
export declare function generateLogoutHeaders(...params: Parameters<typeof clearAuthCookie>): {
|
|
36
|
+
'set-cookie': string;
|
|
37
|
+
"csrf-token": string;
|
|
38
|
+
};
|
|
29
39
|
/**
|
|
30
40
|
* Store auth data on a client (frontend) after receiving an auth response from the host (backend).
|
|
31
41
|
* Specifically, this stores the CSRF token into local storage (which doesn't need to be a secret).
|
package/dist/auth.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { extractCookieJwt,
|
|
1
|
+
import { clearAuthCookie, extractCookieJwt, generateAuthCookie, } from './cookie.js';
|
|
2
2
|
import { csrfTokenHeaderName, generateCsrfToken } from './csrf-token.js';
|
|
3
3
|
function readHeader(headers, headerName) {
|
|
4
4
|
if (headers instanceof Headers) {
|
|
@@ -47,18 +47,30 @@ export async function extractUserIdFromRequestHeaders(headers, jwtParams) {
|
|
|
47
47
|
*
|
|
48
48
|
* @category Auth : Host
|
|
49
49
|
*/
|
|
50
|
-
export async function generateSuccessfulLoginHeaders(
|
|
50
|
+
export async function generateSuccessfulLoginHeaders(
|
|
51
51
|
/** The id from your database of the user you're authenticating. */
|
|
52
|
-
cookieConfig) {
|
|
52
|
+
userId, cookieConfig) {
|
|
53
53
|
const csrfToken = generateCsrfToken();
|
|
54
54
|
return {
|
|
55
|
-
'set-cookie': await
|
|
55
|
+
'set-cookie': await generateAuthCookie({
|
|
56
56
|
csrfToken,
|
|
57
57
|
userId,
|
|
58
58
|
}, cookieConfig),
|
|
59
59
|
[csrfTokenHeaderName]: csrfToken,
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Used by host (backend) code to set headers on a response object when the user has logged out or
|
|
64
|
+
* failed to authorize.
|
|
65
|
+
*
|
|
66
|
+
* @category Auth : Host
|
|
67
|
+
*/
|
|
68
|
+
export function generateLogoutHeaders(...params) {
|
|
69
|
+
return {
|
|
70
|
+
'set-cookie': clearAuthCookie(...params),
|
|
71
|
+
[csrfTokenHeaderName]: 'redacted',
|
|
72
|
+
};
|
|
73
|
+
}
|
|
62
74
|
/**
|
|
63
75
|
* Store auth data on a client (frontend) after receiving an auth response from the host (backend).
|
|
64
76
|
* Specifically, this stores the CSRF token into local storage (which doesn't need to be a secret).
|
package/dist/cookie.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type PartialWithUndefined } from '@augment-vir/common';
|
|
2
2
|
import { type AnyDuration } from 'date-vir';
|
|
3
|
+
import { type Primitive } from 'type-fest';
|
|
3
4
|
import { type CreateJwtParams, type ParseJwtParams } from './jwt.js';
|
|
4
5
|
import { type UserJwtData } from './user-jwt.js';
|
|
5
6
|
/**
|
|
6
|
-
* Parameters for {@link
|
|
7
|
+
* Parameters for {@link generateAuthCookie}.
|
|
7
8
|
*
|
|
8
9
|
* @category Internal
|
|
9
10
|
*/
|
|
@@ -26,6 +27,7 @@ export type CookieParams = {
|
|
|
26
27
|
* client, etc.
|
|
27
28
|
*/
|
|
28
29
|
jwtParams: Readonly<CreateJwtParams>;
|
|
30
|
+
cookieName?: string;
|
|
29
31
|
} & PartialWithUndefined<{
|
|
30
32
|
/**
|
|
31
33
|
* Is set to `true` (which should only be done in development environments), the cookie will be
|
|
@@ -40,7 +42,19 @@ export type CookieParams = {
|
|
|
40
42
|
*
|
|
41
43
|
* @category Internal
|
|
42
44
|
*/
|
|
43
|
-
export declare function
|
|
45
|
+
export declare function generateAuthCookie(userJwtData: Readonly<UserJwtData>, cookieConfig: Readonly<CookieParams>): Promise<string>;
|
|
46
|
+
/**
|
|
47
|
+
* Generate a cookie value that will clear the previous auth cookie. Use this when signing out.
|
|
48
|
+
*
|
|
49
|
+
* @category Internal
|
|
50
|
+
*/
|
|
51
|
+
export declare function clearAuthCookie(cookieConfig: Readonly<Pick<CookieParams, 'cookieName' | 'hostOrigin' | 'isDev'>>): string;
|
|
52
|
+
/**
|
|
53
|
+
* Generate a cookie string from a raw set of parameters.
|
|
54
|
+
*
|
|
55
|
+
* @category Internal
|
|
56
|
+
*/
|
|
57
|
+
export declare function generateCookie(params: Readonly<Record<string, Exclude<Primitive, symbol>>>): string;
|
|
44
58
|
/**
|
|
45
59
|
* Extract an auth cookie from a cookie string. Used in host (backend) code.
|
|
46
60
|
*
|
package/dist/cookie.js
CHANGED
|
@@ -8,16 +8,54 @@ import { createUserJwt, parseUserJwt } from './user-jwt.js';
|
|
|
8
8
|
*
|
|
9
9
|
* @category Internal
|
|
10
10
|
*/
|
|
11
|
-
export async function
|
|
12
|
-
return
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
'
|
|
18
|
-
|
|
19
|
-
cookieConfig.isDev
|
|
20
|
-
|
|
11
|
+
export async function generateAuthCookie(userJwtData, cookieConfig) {
|
|
12
|
+
return generateCookie({
|
|
13
|
+
[cookieConfig.cookieName || 'auth']: await createUserJwt(userJwtData, cookieConfig.jwtParams),
|
|
14
|
+
Domain: parseUrl(cookieConfig.hostOrigin).hostname,
|
|
15
|
+
HttpOnly: true,
|
|
16
|
+
Path: '/',
|
|
17
|
+
SameSite: 'Strict',
|
|
18
|
+
'MAX-AGE': convertDuration(cookieConfig.cookieDuration, { seconds: true }).seconds,
|
|
19
|
+
Secure: !cookieConfig.isDev,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Generate a cookie value that will clear the previous auth cookie. Use this when signing out.
|
|
24
|
+
*
|
|
25
|
+
* @category Internal
|
|
26
|
+
*/
|
|
27
|
+
export function clearAuthCookie(cookieConfig) {
|
|
28
|
+
return generateCookie({
|
|
29
|
+
[cookieConfig.cookieName || 'auth']: 'redacted',
|
|
30
|
+
Domain: parseUrl(cookieConfig.hostOrigin).hostname,
|
|
31
|
+
HttpOnly: true,
|
|
32
|
+
Path: '/',
|
|
33
|
+
SameSite: 'Strict',
|
|
34
|
+
'MAX-AGE': 0,
|
|
35
|
+
Secure: !cookieConfig.isDev,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Generate a cookie string from a raw set of parameters.
|
|
40
|
+
*
|
|
41
|
+
* @category Internal
|
|
42
|
+
*/
|
|
43
|
+
export function generateCookie(params) {
|
|
44
|
+
return Object.entries(params)
|
|
45
|
+
.map(([key, value,]) => {
|
|
46
|
+
if (value == undefined || value === false) {
|
|
47
|
+
return undefined;
|
|
48
|
+
}
|
|
49
|
+
else if (value === '' || value === true) {
|
|
50
|
+
return key;
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
return [
|
|
54
|
+
key,
|
|
55
|
+
value,
|
|
56
|
+
].join('=');
|
|
57
|
+
}
|
|
58
|
+
})
|
|
21
59
|
.filter(check.isTruthy)
|
|
22
60
|
.join('; ');
|
|
23
61
|
}
|
package/dist/hash.d.ts
CHANGED
|
@@ -1,20 +1,27 @@
|
|
|
1
|
+
import { type PartialWithUndefined } from '@augment-vir/common';
|
|
2
|
+
import { type IArgon2Options } from 'hash-wasm';
|
|
1
3
|
/**
|
|
2
|
-
*
|
|
3
|
-
* The output of this function is safe to store in a database for future credential comparisons.
|
|
4
|
+
* Default value for {@link HashPasswordOptions}.
|
|
4
5
|
*
|
|
5
|
-
* @category
|
|
6
|
-
* @returns `undefined` if the password is too long (and would be truncated by the bcrypt hashing
|
|
7
|
-
* algorithm). Otherwise, the hashed output.
|
|
8
|
-
* @see https://wikipedia.org/wiki/Bcrypt
|
|
6
|
+
* @category Internal
|
|
9
7
|
*/
|
|
10
|
-
export declare
|
|
8
|
+
export declare const defaultHashOptions: HashPasswordOptions;
|
|
11
9
|
/**
|
|
12
|
-
*
|
|
13
|
-
* longer than this should not be accepted.
|
|
10
|
+
* Options for {@link hashPassword}.
|
|
14
11
|
*
|
|
15
12
|
* @category Internal
|
|
16
13
|
*/
|
|
17
|
-
export
|
|
14
|
+
export type HashPasswordOptions = PartialWithUndefined<Omit<IArgon2Options, 'outputType' | 'salt' | 'password' | 'secret'>>;
|
|
15
|
+
/**
|
|
16
|
+
* Hashes a password using the Argon2id algorithm so passwords don't need to be stored in plain
|
|
17
|
+
* text. The output of this function is safe to store in a database for future credential
|
|
18
|
+
* comparisons.
|
|
19
|
+
*
|
|
20
|
+
* @category Auth : Host
|
|
21
|
+
* @returns The hashed password.
|
|
22
|
+
* @see https://en.wikipedia.org/wiki/Argon2
|
|
23
|
+
*/
|
|
24
|
+
export declare function hashPassword(password: string, options?: HashPasswordOptions): Promise<string>;
|
|
18
25
|
/**
|
|
19
26
|
* A utility that provides more accurate string byte size than doing `string.length`.
|
|
20
27
|
*
|
|
@@ -22,7 +29,7 @@ export declare function willHashTruncate(input: string): boolean;
|
|
|
22
29
|
*/
|
|
23
30
|
export declare function getByteLength(input: string): number;
|
|
24
31
|
/**
|
|
25
|
-
* Checks if the given password is a match by comparing it to
|
|
32
|
+
* Checks if the given password is a match by comparing it to the previously computed and stored
|
|
26
33
|
* hash.
|
|
27
34
|
*
|
|
28
35
|
* @category Auth : Host
|
package/dist/hash.js
CHANGED
|
@@ -1,33 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mergeDefinedProperties, } from '@augment-vir/common';
|
|
2
|
+
import { argon2id, argon2Verify } from 'hash-wasm';
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
* The output of this function is safe to store in a database for future credential comparisons.
|
|
4
|
+
* Default value for {@link HashPasswordOptions}.
|
|
5
5
|
*
|
|
6
|
-
* @category
|
|
7
|
-
* @returns `undefined` if the password is too long (and would be truncated by the bcrypt hashing
|
|
8
|
-
* algorithm). Otherwise, the hashed output.
|
|
9
|
-
* @see https://wikipedia.org/wiki/Bcrypt
|
|
6
|
+
* @category Internal
|
|
10
7
|
*/
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return await bcrypt({
|
|
18
|
-
costFactor: 10,
|
|
19
|
-
password: password.normalize(),
|
|
20
|
-
salt,
|
|
21
|
-
});
|
|
22
|
-
}
|
|
8
|
+
export const defaultHashOptions = {
|
|
9
|
+
hashLength: 32,
|
|
10
|
+
iterations: 256,
|
|
11
|
+
memorySize: 512,
|
|
12
|
+
parallelism: 1,
|
|
13
|
+
};
|
|
23
14
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
15
|
+
* Hashes a password using the Argon2id algorithm so passwords don't need to be stored in plain
|
|
16
|
+
* text. The output of this function is safe to store in a database for future credential
|
|
17
|
+
* comparisons.
|
|
26
18
|
*
|
|
27
|
-
* @category
|
|
19
|
+
* @category Auth : Host
|
|
20
|
+
* @returns The hashed password.
|
|
21
|
+
* @see https://en.wikipedia.org/wiki/Argon2
|
|
28
22
|
*/
|
|
29
|
-
export function
|
|
30
|
-
|
|
23
|
+
export async function hashPassword(password, options = {}) {
|
|
24
|
+
const salt = globalThis.crypto.getRandomValues(new Uint8Array(16));
|
|
25
|
+
return await argon2id(mergeDefinedProperties(defaultHashOptions, options, {
|
|
26
|
+
outputType: 'encoded',
|
|
27
|
+
password: password.normalize(),
|
|
28
|
+
salt,
|
|
29
|
+
}));
|
|
31
30
|
}
|
|
32
31
|
/**
|
|
33
32
|
* A utility that provides more accurate string byte size than doing `string.length`.
|
|
@@ -38,13 +37,13 @@ export function getByteLength(input) {
|
|
|
38
37
|
return new Blob([input]).size;
|
|
39
38
|
}
|
|
40
39
|
/**
|
|
41
|
-
* Checks if the given password is a match by comparing it to
|
|
40
|
+
* Checks if the given password is a match by comparing it to the previously computed and stored
|
|
42
41
|
* hash.
|
|
43
42
|
*
|
|
44
43
|
* @category Auth : Host
|
|
45
44
|
*/
|
|
46
45
|
export async function doesPasswordMatchHash({ password, hash, }) {
|
|
47
|
-
return await
|
|
46
|
+
return await argon2Verify({
|
|
48
47
|
hash,
|
|
49
48
|
password,
|
|
50
49
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "auth-vir",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Auth made easy and secure via JWT cookies, CSRF tokens, and password hashing helpers.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"auth",
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"hash-wasm": "^4.12.0",
|
|
47
47
|
"jose": "^6.0.11",
|
|
48
48
|
"object-shape-tester": "^5.1.5",
|
|
49
|
+
"type-fest": "^4.41.0",
|
|
49
50
|
"url-vir": "^2.1.3"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
package/src/auth.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
clearAuthCookie,
|
|
3
|
+
type CookieParams,
|
|
4
|
+
extractCookieJwt,
|
|
5
|
+
generateAuthCookie,
|
|
6
|
+
} from './cookie.js';
|
|
2
7
|
import {csrfTokenHeaderName, generateCsrfToken} from './csrf-token.js';
|
|
3
8
|
import {type ParseJwtParams} from './jwt.js';
|
|
4
9
|
|
|
@@ -63,14 +68,14 @@ export async function extractUserIdFromRequestHeaders(
|
|
|
63
68
|
* @category Auth : Host
|
|
64
69
|
*/
|
|
65
70
|
export async function generateSuccessfulLoginHeaders(
|
|
66
|
-
userId: string,
|
|
67
71
|
/** The id from your database of the user you're authenticating. */
|
|
72
|
+
userId: string,
|
|
68
73
|
cookieConfig: Readonly<CookieParams>,
|
|
69
74
|
) {
|
|
70
75
|
const csrfToken = generateCsrfToken();
|
|
71
76
|
|
|
72
77
|
return {
|
|
73
|
-
'set-cookie': await
|
|
78
|
+
'set-cookie': await generateAuthCookie(
|
|
74
79
|
{
|
|
75
80
|
csrfToken,
|
|
76
81
|
userId,
|
|
@@ -81,6 +86,19 @@ export async function generateSuccessfulLoginHeaders(
|
|
|
81
86
|
};
|
|
82
87
|
}
|
|
83
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Used by host (backend) code to set headers on a response object when the user has logged out or
|
|
91
|
+
* failed to authorize.
|
|
92
|
+
*
|
|
93
|
+
* @category Auth : Host
|
|
94
|
+
*/
|
|
95
|
+
export function generateLogoutHeaders(...params: Parameters<typeof clearAuthCookie>) {
|
|
96
|
+
return {
|
|
97
|
+
'set-cookie': clearAuthCookie(...params),
|
|
98
|
+
[csrfTokenHeaderName]: 'redacted',
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
84
102
|
/**
|
|
85
103
|
* Store auth data on a client (frontend) after receiving an auth response from the host (backend).
|
|
86
104
|
* Specifically, this stores the CSRF token into local storage (which doesn't need to be a secret).
|
package/src/cookie.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import {check} from '@augment-vir/assert';
|
|
2
2
|
import {safeMatch, type PartialWithUndefined} from '@augment-vir/common';
|
|
3
3
|
import {convertDuration, type AnyDuration} from 'date-vir';
|
|
4
|
+
import {type Primitive} from 'type-fest';
|
|
4
5
|
import {parseUrl} from 'url-vir';
|
|
5
6
|
import {type CreateJwtParams, type ParseJwtParams} from './jwt.js';
|
|
6
7
|
import {createUserJwt, parseUserJwt, type UserJwtData} from './user-jwt.js';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
|
-
* Parameters for {@link
|
|
10
|
+
* Parameters for {@link generateAuthCookie}.
|
|
10
11
|
*
|
|
11
12
|
* @category Internal
|
|
12
13
|
*/
|
|
@@ -29,6 +30,7 @@ export type CookieParams = {
|
|
|
29
30
|
* client, etc.
|
|
30
31
|
*/
|
|
31
32
|
jwtParams: Readonly<CreateJwtParams>;
|
|
33
|
+
cookieName?: string;
|
|
32
34
|
} & PartialWithUndefined<{
|
|
33
35
|
/**
|
|
34
36
|
* Is set to `true` (which should only be done in development environments), the cookie will be
|
|
@@ -44,19 +46,69 @@ export type CookieParams = {
|
|
|
44
46
|
*
|
|
45
47
|
* @category Internal
|
|
46
48
|
*/
|
|
47
|
-
export async function
|
|
49
|
+
export async function generateAuthCookie(
|
|
48
50
|
userJwtData: Readonly<UserJwtData>,
|
|
49
51
|
cookieConfig: Readonly<CookieParams>,
|
|
52
|
+
): Promise<string> {
|
|
53
|
+
return generateCookie({
|
|
54
|
+
[cookieConfig.cookieName || 'auth']: await createUserJwt(
|
|
55
|
+
userJwtData,
|
|
56
|
+
cookieConfig.jwtParams,
|
|
57
|
+
),
|
|
58
|
+
Domain: parseUrl(cookieConfig.hostOrigin).hostname,
|
|
59
|
+
HttpOnly: true,
|
|
60
|
+
Path: '/',
|
|
61
|
+
SameSite: 'Strict',
|
|
62
|
+
'MAX-AGE': convertDuration(cookieConfig.cookieDuration, {seconds: true}).seconds,
|
|
63
|
+
Secure: !cookieConfig.isDev,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Generate a cookie value that will clear the previous auth cookie. Use this when signing out.
|
|
69
|
+
*
|
|
70
|
+
* @category Internal
|
|
71
|
+
*/
|
|
72
|
+
export function clearAuthCookie(
|
|
73
|
+
cookieConfig: Readonly<Pick<CookieParams, 'cookieName' | 'hostOrigin' | 'isDev'>>,
|
|
50
74
|
) {
|
|
51
|
-
return
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
'
|
|
57
|
-
|
|
58
|
-
cookieConfig.isDev
|
|
59
|
-
|
|
75
|
+
return generateCookie({
|
|
76
|
+
[cookieConfig.cookieName || 'auth']: 'redacted',
|
|
77
|
+
Domain: parseUrl(cookieConfig.hostOrigin).hostname,
|
|
78
|
+
HttpOnly: true,
|
|
79
|
+
Path: '/',
|
|
80
|
+
SameSite: 'Strict',
|
|
81
|
+
'MAX-AGE': 0,
|
|
82
|
+
Secure: !cookieConfig.isDev,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Generate a cookie string from a raw set of parameters.
|
|
88
|
+
*
|
|
89
|
+
* @category Internal
|
|
90
|
+
*/
|
|
91
|
+
export function generateCookie(
|
|
92
|
+
params: Readonly<Record<string, Exclude<Primitive, symbol>>>,
|
|
93
|
+
): string {
|
|
94
|
+
return Object.entries(params)
|
|
95
|
+
.map(
|
|
96
|
+
([
|
|
97
|
+
key,
|
|
98
|
+
value,
|
|
99
|
+
]): string | undefined => {
|
|
100
|
+
if (value == undefined || value === false) {
|
|
101
|
+
return undefined;
|
|
102
|
+
} else if (value === '' || value === true) {
|
|
103
|
+
return key;
|
|
104
|
+
} else {
|
|
105
|
+
return [
|
|
106
|
+
key,
|
|
107
|
+
value,
|
|
108
|
+
].join('=');
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
)
|
|
60
112
|
.filter(check.isTruthy)
|
|
61
113
|
.join('; ');
|
|
62
114
|
}
|
package/src/hash.ts
CHANGED
|
@@ -1,37 +1,53 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type AnyObject,
|
|
3
|
+
mergeDefinedProperties,
|
|
4
|
+
type PartialWithUndefined,
|
|
5
|
+
} from '@augment-vir/common';
|
|
6
|
+
import {argon2id, argon2Verify, type IArgon2Options} from 'hash-wasm';
|
|
2
7
|
|
|
3
8
|
/**
|
|
4
|
-
*
|
|
5
|
-
* The output of this function is safe to store in a database for future credential comparisons.
|
|
9
|
+
* Default value for {@link HashPasswordOptions}.
|
|
6
10
|
*
|
|
7
|
-
* @category
|
|
8
|
-
* @returns `undefined` if the password is too long (and would be truncated by the bcrypt hashing
|
|
9
|
-
* algorithm). Otherwise, the hashed output.
|
|
10
|
-
* @see https://wikipedia.org/wiki/Bcrypt
|
|
11
|
+
* @category Internal
|
|
11
12
|
*/
|
|
12
|
-
export
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
globalThis.crypto.getRandomValues(salt);
|
|
19
|
-
|
|
20
|
-
return await bcrypt({
|
|
21
|
-
costFactor: 10,
|
|
22
|
-
password: password.normalize(),
|
|
23
|
-
salt,
|
|
24
|
-
});
|
|
25
|
-
}
|
|
13
|
+
export const defaultHashOptions: HashPasswordOptions = {
|
|
14
|
+
hashLength: 32,
|
|
15
|
+
iterations: 256,
|
|
16
|
+
memorySize: 512,
|
|
17
|
+
parallelism: 1,
|
|
18
|
+
};
|
|
26
19
|
|
|
27
20
|
/**
|
|
28
|
-
*
|
|
29
|
-
* longer than this should not be accepted.
|
|
21
|
+
* Options for {@link hashPassword}.
|
|
30
22
|
*
|
|
31
23
|
* @category Internal
|
|
32
24
|
*/
|
|
33
|
-
export
|
|
34
|
-
|
|
25
|
+
export type HashPasswordOptions = PartialWithUndefined<
|
|
26
|
+
Omit<IArgon2Options, 'outputType' | 'salt' | 'password' | 'secret'>
|
|
27
|
+
>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Hashes a password using the Argon2id algorithm so passwords don't need to be stored in plain
|
|
31
|
+
* text. The output of this function is safe to store in a database for future credential
|
|
32
|
+
* comparisons.
|
|
33
|
+
*
|
|
34
|
+
* @category Auth : Host
|
|
35
|
+
* @returns The hashed password.
|
|
36
|
+
* @see https://en.wikipedia.org/wiki/Argon2
|
|
37
|
+
*/
|
|
38
|
+
export async function hashPassword(
|
|
39
|
+
password: string,
|
|
40
|
+
options: HashPasswordOptions = {},
|
|
41
|
+
): Promise<string> {
|
|
42
|
+
const salt = globalThis.crypto.getRandomValues(new Uint8Array(16));
|
|
43
|
+
|
|
44
|
+
return await argon2id(
|
|
45
|
+
mergeDefinedProperties<AnyObject>(defaultHashOptions, options, {
|
|
46
|
+
outputType: 'encoded',
|
|
47
|
+
password: password.normalize(),
|
|
48
|
+
salt,
|
|
49
|
+
}) as IArgon2Options,
|
|
50
|
+
);
|
|
35
51
|
}
|
|
36
52
|
|
|
37
53
|
/**
|
|
@@ -44,7 +60,7 @@ export function getByteLength(input: string): number {
|
|
|
44
60
|
}
|
|
45
61
|
|
|
46
62
|
/**
|
|
47
|
-
* Checks if the given password is a match by comparing it to
|
|
63
|
+
* Checks if the given password is a match by comparing it to the previously computed and stored
|
|
48
64
|
* hash.
|
|
49
65
|
*
|
|
50
66
|
* @category Auth : Host
|
|
@@ -58,7 +74,7 @@ export async function doesPasswordMatchHash({
|
|
|
58
74
|
/** The stored password hash for that user. */
|
|
59
75
|
hash: string;
|
|
60
76
|
}): Promise<boolean> {
|
|
61
|
-
return await
|
|
77
|
+
return await argon2Verify({
|
|
62
78
|
hash,
|
|
63
79
|
password,
|
|
64
80
|
});
|