auth-vir 1.0.0 → 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/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(userId: string,
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, generateCookie } from './cookie.js';
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(userId,
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 generateCookie({
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 generateCookie}.
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 generateCookie(userJwtData: Readonly<UserJwtData>, cookieConfig: Readonly<CookieParams>): Promise<string>;
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 generateCookie(userJwtData, cookieConfig) {
12
- return [
13
- `auth=${await createUserJwt(userJwtData, cookieConfig.jwtParams)}`,
14
- `Domain=${parseUrl(cookieConfig.hostOrigin).hostname}`,
15
- 'HttpOnly',
16
- 'Path=/',
17
- 'SameSite=Strict',
18
- `MAX-AGE=${convertDuration(cookieConfig.cookieDuration, { seconds: true }).seconds}`,
19
- cookieConfig.isDev ? '' : 'Secure',
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "auth-vir",
3
- "version": "1.0.0",
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 {type CookieParams, extractCookieJwt, generateCookie} from './cookie.js';
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 generateCookie(
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 generateCookie}.
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 generateCookie(
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
- `auth=${await createUserJwt(userJwtData, cookieConfig.jwtParams)}`,
53
- `Domain=${parseUrl(cookieConfig.hostOrigin).hostname}`,
54
- 'HttpOnly',
55
- 'Path=/',
56
- 'SameSite=Strict',
57
- `MAX-AGE=${convertDuration(cookieConfig.cookieDuration, {seconds: true}).seconds}`,
58
- cookieConfig.isDev ? '' : 'Secure',
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
  }