@aura-stack/auth 0.1.0-rc.8 → 0.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/@types/index.cjs +15 -15
- package/dist/@types/index.d.ts +7 -31
- package/dist/@types/index.js +1 -1
- package/dist/@types/router.d.cjs +1 -1
- package/dist/@types/router.d.d.ts +7 -7
- package/dist/@types/utility.cjs +15 -15
- package/dist/@types/utility.d.ts +6 -6
- package/dist/@types/utility.js +1 -1
- package/dist/actions/callback/access-token.cjs +143 -143
- package/dist/actions/callback/access-token.d.ts +15 -20
- package/dist/actions/callback/access-token.js +8 -4
- package/dist/actions/callback/callback.cjs +437 -455
- package/dist/actions/callback/callback.d.ts +10 -12
- package/dist/actions/callback/callback.js +16 -12
- package/dist/actions/callback/userinfo.cjs +131 -134
- package/dist/actions/callback/userinfo.d.ts +9 -9
- package/dist/actions/callback/userinfo.js +10 -6
- package/dist/actions/csrfToken/csrfToken.cjs +162 -171
- package/dist/actions/csrfToken/csrfToken.d.ts +3 -3
- package/dist/actions/csrfToken/csrfToken.js +12 -8
- package/dist/actions/index.cjs +746 -773
- package/dist/actions/index.d.ts +13 -13
- package/dist/actions/index.js +34 -18
- package/dist/actions/session/session.cjs +149 -155
- package/dist/actions/session/session.d.ts +3 -3
- package/dist/actions/session/session.js +11 -7
- package/dist/actions/signIn/authorization.cjs +231 -249
- package/dist/actions/signIn/authorization.d.ts +12 -18
- package/dist/actions/signIn/authorization.js +16 -6
- package/dist/actions/signIn/signIn.cjs +396 -423
- package/dist/actions/signIn/signIn.d.ts +10 -10
- package/dist/actions/signIn/signIn.js +14 -10
- package/dist/actions/signOut/signOut.cjs +417 -440
- package/dist/actions/signOut/signOut.d.ts +3 -3
- package/dist/actions/signOut/signOut.js +15 -11
- package/dist/assert.cjs +35 -36
- package/dist/assert.d.ts +4 -4
- package/dist/assert.js +10 -2
- package/dist/chunk-256KIVJL.js +85 -96
- package/dist/chunk-42XB3YCW.js +19 -17
- package/dist/chunk-6SM22VVJ.js +13 -10
- package/dist/chunk-CAKJT3KS.js +84 -69
- package/dist/chunk-E3OXBRYF.js +19 -17
- package/dist/chunk-EBPE35JT.js +28 -26
- package/dist/chunk-FIPU4MLT.js +18 -16
- package/dist/chunk-FJUDBLCP.js +50 -43
- package/dist/chunk-FKRDCWBF.js +19 -17
- package/dist/chunk-GZU3RBTB.js +51 -40
- package/dist/chunk-HGJ4TXY4.js +132 -100
- package/dist/chunk-HMRKN75I.js +63 -63
- package/dist/chunk-IKHPGFCW.js +11 -9
- package/dist/chunk-JAPMIE6S.js +7 -5
- package/dist/chunk-KRNOMBXQ.js +19 -17
- package/dist/chunk-LLR722CL.js +91 -70
- package/dist/chunk-RLT4RFKV.js +39 -30
- package/dist/chunk-SJPDVKUS.js +107 -88
- package/dist/chunk-SMQO5WD7.js +26 -16
- package/dist/chunk-STHEPPUZ.js +8 -6
- package/dist/chunk-UJJ7R56J.js +47 -37
- package/dist/chunk-UTDLUEEG.js +27 -21
- package/dist/chunk-VFTYH33W.js +54 -37
- package/dist/chunk-XXJKNKGQ.js +33 -23
- package/dist/chunk-ZV4BH47P.js +132 -134
- package/dist/cookie.cjs +169 -175
- package/dist/cookie.d.ts +23 -51
- package/dist/cookie.js +34 -34
- package/dist/error.cjs +75 -75
- package/dist/error.d.ts +30 -30
- package/dist/error.js +15 -8
- package/dist/headers.cjs +28 -28
- package/dist/headers.d.ts +2 -2
- package/dist/headers.js +6 -2
- package/dist/index-DpfbvTZ_.d.ts +249 -298
- package/dist/index.cjs +936 -969
- package/dist/index.d.ts +10 -31
- package/dist/index.js +70 -50
- package/dist/jose.cjs +61 -64
- package/dist/jose.d.ts +8 -8
- package/dist/jose.js +9 -5
- package/dist/oauth/bitbucket.cjs +38 -38
- package/dist/oauth/bitbucket.d.ts +7 -7
- package/dist/oauth/bitbucket.js +6 -2
- package/dist/oauth/discord.cjs +48 -48
- package/dist/oauth/discord.d.ts +7 -7
- package/dist/oauth/discord.js +6 -2
- package/dist/oauth/figma.cjs +39 -39
- package/dist/oauth/figma.d.ts +7 -7
- package/dist/oauth/figma.js +6 -2
- package/dist/oauth/github.cjs +31 -31
- package/dist/oauth/github.d.ts +7 -7
- package/dist/oauth/github.js +6 -2
- package/dist/oauth/gitlab.cjs +39 -39
- package/dist/oauth/gitlab.d.ts +7 -7
- package/dist/oauth/gitlab.js +6 -2
- package/dist/oauth/index.cjs +180 -180
- package/dist/oauth/index.d.ts +7 -26
- package/dist/oauth/index.js +36 -9
- package/dist/oauth/spotify.cjs +39 -39
- package/dist/oauth/spotify.d.ts +7 -7
- package/dist/oauth/spotify.js +6 -2
- package/dist/oauth/x.cjs +39 -39
- package/dist/oauth/x.d.ts +7 -7
- package/dist/oauth/x.js +6 -2
- package/dist/response.cjs +27 -27
- package/dist/response.d.ts +2 -2
- package/dist/response.js +6 -2
- package/dist/schemas.cjs +91 -91
- package/dist/schemas.d.ts +93 -126
- package/dist/schemas.js +18 -18
- package/dist/secure.cjs +95 -98
- package/dist/secure.d.ts +17 -17
- package/dist/secure.js +18 -4
- package/dist/utils.cjs +119 -132
- package/dist/utils.d.ts +11 -26
- package/dist/utils.js +21 -21
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -1,1125 +1,1092 @@
|
|
|
1
|
-
"use strict"
|
|
2
|
-
var __create = Object.create
|
|
3
|
-
var __defProp = Object.defineProperty
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
8
|
var __export = (target, all) => {
|
|
9
|
-
|
|
10
|
-
}
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
11
12
|
var __copyProps = (to, from, except, desc) => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
}
|
|
19
|
-
var __toESM = (mod, isNodeMode, target) => (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
)
|
|
29
|
-
)
|
|
30
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod)
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
31
29
|
|
|
32
30
|
// src/index.ts
|
|
33
|
-
var index_exports = {}
|
|
31
|
+
var index_exports = {};
|
|
34
32
|
__export(index_exports, {
|
|
35
|
-
|
|
36
|
-
})
|
|
37
|
-
module.exports = __toCommonJS(index_exports)
|
|
38
|
-
var import_config2 = require("dotenv/config")
|
|
39
|
-
var import_router7 = require("@aura-stack/router")
|
|
33
|
+
createAuth: () => createAuth
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
var import_config2 = require("dotenv/config");
|
|
37
|
+
var import_router7 = require("@aura-stack/router");
|
|
40
38
|
|
|
41
39
|
// src/utils.ts
|
|
42
|
-
var import_router = require("@aura-stack/router")
|
|
40
|
+
var import_router = require("@aura-stack/router");
|
|
43
41
|
|
|
44
42
|
// src/error.ts
|
|
45
43
|
var AuthError = class extends Error {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
44
|
+
constructor(type, message) {
|
|
45
|
+
super(message);
|
|
46
|
+
this.type = type;
|
|
47
|
+
this.name = "AuthError";
|
|
48
|
+
}
|
|
49
|
+
};
|
|
52
50
|
var InvalidCsrfTokenError = class extends AuthError {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
51
|
+
constructor(message = "The provided CSRF token is invalid or has expired") {
|
|
52
|
+
super("invalid_csrf_token", message);
|
|
53
|
+
this.name = "InvalidCsrfTokenError";
|
|
54
|
+
}
|
|
55
|
+
};
|
|
58
56
|
var InvalidRedirectToError = class extends AuthError {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
}
|
|
57
|
+
constructor(message = "The redirectTo parameter does not match the hosted origin.") {
|
|
58
|
+
super("invalid_redirect_to", message);
|
|
59
|
+
this.name = "InvalidRedirectToError";
|
|
60
|
+
}
|
|
61
|
+
};
|
|
64
62
|
var isAuthError = (error) => {
|
|
65
|
-
|
|
66
|
-
}
|
|
63
|
+
return error instanceof AuthError;
|
|
64
|
+
};
|
|
67
65
|
var throwAuthError = (error, message) => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
throw new AuthError("invalid_request", error.message ?? message)
|
|
66
|
+
if (error instanceof Error) {
|
|
67
|
+
if (isAuthError(error)) {
|
|
68
|
+
throw error;
|
|
73
69
|
}
|
|
74
|
-
|
|
70
|
+
throw new AuthError("invalid_request", error.message ?? message);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
75
73
|
var ERROR_RESPONSE = {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
74
|
+
AUTHORIZATION: {
|
|
75
|
+
INVALID_REQUEST: "invalid_request",
|
|
76
|
+
UNAUTHORIZED_CLIENT: "unauthorized_client",
|
|
77
|
+
ACCESS_DENIED: "access_denied",
|
|
78
|
+
UNSUPPORTED_RESPONSE_TYPE: "unsupported_response_type",
|
|
79
|
+
INVALID_SCOPE: "invalid_scope",
|
|
80
|
+
SERVER_ERROR: "server_error",
|
|
81
|
+
TEMPORARILY_UNAVAILABLE: "temporarily_unavailable"
|
|
82
|
+
},
|
|
83
|
+
ACCESS_TOKEN: {
|
|
84
|
+
INVALID_REQUEST: "invalid_request",
|
|
85
|
+
INVALID_CLIENT: "invalid_client",
|
|
86
|
+
INVALID_GRANT: "invalid_grant",
|
|
87
|
+
UNAUTHORIZED_CLIENT: "unauthorized_client",
|
|
88
|
+
UNSUPPORTED_GRANT_TYPE: "unsupported_grant_type",
|
|
89
|
+
INVALID_SCOPE: "invalid_scope"
|
|
90
|
+
}
|
|
91
|
+
};
|
|
94
92
|
|
|
95
93
|
// src/utils.ts
|
|
96
94
|
var toSnakeCase = (str) => {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
.replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2")
|
|
100
|
-
.toLowerCase()
|
|
101
|
-
.replace(/^_+/, "")
|
|
102
|
-
}
|
|
95
|
+
return str.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1_$2").toLowerCase().replace(/^_+/, "");
|
|
96
|
+
};
|
|
103
97
|
var toUpperCase = (str) => {
|
|
104
|
-
|
|
105
|
-
}
|
|
98
|
+
return str.toUpperCase();
|
|
99
|
+
};
|
|
106
100
|
var toCastCase = (obj, type = "snake") => {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
101
|
+
return Object.entries(obj).reduce((previous, [key, value]) => {
|
|
102
|
+
const newKey = type === "snake" ? toSnakeCase(key) : toUpperCase(key);
|
|
103
|
+
return { ...previous, [newKey]: value };
|
|
104
|
+
}, {});
|
|
105
|
+
};
|
|
112
106
|
var equals = (a, b) => {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
107
|
+
if (a === null || b === null || a === void 0 || b === void 0) return false;
|
|
108
|
+
return a === b;
|
|
109
|
+
};
|
|
116
110
|
var sanitizeURL = (url2) => {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
let sanitized = decodedURL
|
|
144
|
-
.replace(/\/\.\.\//g, "/")
|
|
145
|
-
.replace(/\/\.\.$/, "")
|
|
146
|
-
.replace(/\.{2,}/g, "")
|
|
147
|
-
.replace(/\/{2,}/g, "/")
|
|
148
|
-
if (sanitized !== "/" && sanitized.endsWith("/")) {
|
|
149
|
-
sanitized = sanitized.replace(/\/+$/, "/")
|
|
150
|
-
} else if (sanitized !== "/") {
|
|
151
|
-
sanitized = sanitized.replace(/\/+$/, "")
|
|
152
|
-
}
|
|
153
|
-
return sanitized
|
|
154
|
-
} catch {
|
|
155
|
-
return url2.trim()
|
|
111
|
+
try {
|
|
112
|
+
let decodedURL = decodeURIComponent(url2).trim();
|
|
113
|
+
const protocolMatch = decodedURL.match(/^([a-zA-Z][a-zA-Z0-9+.-]*:\/\/)/);
|
|
114
|
+
let protocol = "";
|
|
115
|
+
let rest = decodedURL;
|
|
116
|
+
if (protocolMatch) {
|
|
117
|
+
protocol = protocolMatch[1];
|
|
118
|
+
rest = decodedURL.slice(protocol.length);
|
|
119
|
+
const slashIndex = rest.indexOf("/");
|
|
120
|
+
if (slashIndex === -1) {
|
|
121
|
+
return protocol + rest;
|
|
122
|
+
}
|
|
123
|
+
const domain = rest.slice(0, slashIndex);
|
|
124
|
+
let path = rest.slice(slashIndex).replace(/\/\.\.\//g, "/").replace(/\/\.\.$/, "").replace(/\.{2,}/g, "").replace(/\/{2,}/g, "/");
|
|
125
|
+
if (path !== "/" && path.endsWith("/")) {
|
|
126
|
+
path = path.replace(/\/+$/, "/");
|
|
127
|
+
} else if (path !== "/") {
|
|
128
|
+
path = path.replace(/\/+$/, "");
|
|
129
|
+
}
|
|
130
|
+
return protocol + domain + path;
|
|
131
|
+
}
|
|
132
|
+
let sanitized = decodedURL.replace(/\/\.\.\//g, "/").replace(/\/\.\.$/, "").replace(/\.{2,}/g, "").replace(/\/{2,}/g, "/");
|
|
133
|
+
if (sanitized !== "/" && sanitized.endsWith("/")) {
|
|
134
|
+
sanitized = sanitized.replace(/\/+$/, "/");
|
|
135
|
+
} else if (sanitized !== "/") {
|
|
136
|
+
sanitized = sanitized.replace(/\/+$/, "");
|
|
156
137
|
}
|
|
157
|
-
|
|
138
|
+
return sanitized;
|
|
139
|
+
} catch {
|
|
140
|
+
return url2.trim();
|
|
141
|
+
}
|
|
142
|
+
};
|
|
158
143
|
var isValidRelativePath = (path) => {
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
144
|
+
if (!path || typeof path !== "string") return false;
|
|
145
|
+
if (!path.startsWith("/") || path.includes("://") || path.includes("\r") || path.includes("\n")) return false;
|
|
146
|
+
if (/[\x00-\x1F\x7F]/.test(path) || path.includes("\0")) return false;
|
|
147
|
+
const sanitized = sanitizeURL(path);
|
|
148
|
+
if (sanitized.includes("..")) return false;
|
|
149
|
+
return true;
|
|
150
|
+
};
|
|
166
151
|
var onErrorHandler = (error) => {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
}
|
|
152
|
+
if ((0, import_router.isRouterError)(error)) {
|
|
153
|
+
const { message, status, statusText } = error;
|
|
154
|
+
return Response.json({ error: "invalid_request", error_description: message }, { status, statusText });
|
|
155
|
+
}
|
|
156
|
+
if (isAuthError(error)) {
|
|
157
|
+
const { type, message } = error;
|
|
158
|
+
return Response.json({ error: type, error_description: message }, { status: 400 });
|
|
159
|
+
}
|
|
160
|
+
return Response.json({ error: "server_error", error_description: "An unexpected error occurred" }, { status: 500 });
|
|
161
|
+
};
|
|
177
162
|
var getNormalizedOriginPath = (path) => {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
163
|
+
try {
|
|
164
|
+
const url2 = new URL(path);
|
|
165
|
+
url2.hash = "";
|
|
166
|
+
url2.search = "";
|
|
167
|
+
return `${url2.origin}${url2.pathname}`;
|
|
168
|
+
} catch {
|
|
169
|
+
return sanitizeURL(path);
|
|
170
|
+
}
|
|
171
|
+
};
|
|
187
172
|
var toISOString = (date) => {
|
|
188
|
-
|
|
189
|
-
}
|
|
173
|
+
return new Date(date).toISOString();
|
|
174
|
+
};
|
|
190
175
|
|
|
191
176
|
// src/jose.ts
|
|
192
|
-
var import_config = require("dotenv/config")
|
|
193
|
-
var import_jose = require("@aura-stack/jose")
|
|
177
|
+
var import_config = require("dotenv/config");
|
|
178
|
+
var import_jose = require("@aura-stack/jose");
|
|
194
179
|
|
|
195
180
|
// src/secure.ts
|
|
196
|
-
var import_node_crypto = __toESM(require("crypto"), 1)
|
|
181
|
+
var import_node_crypto = __toESM(require("crypto"), 1);
|
|
197
182
|
var generateSecure = (length = 32) => {
|
|
198
|
-
|
|
199
|
-
}
|
|
183
|
+
return import_node_crypto.default.randomBytes(length).toString("base64url");
|
|
184
|
+
};
|
|
200
185
|
var createHash = (data, base = "hex") => {
|
|
201
|
-
|
|
202
|
-
}
|
|
186
|
+
return import_node_crypto.default.createHash("sha256").update(data).digest().toString(base);
|
|
187
|
+
};
|
|
203
188
|
var createPKCE = async (verifier) => {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
189
|
+
const codeVerifier = verifier ?? generateSecure(86);
|
|
190
|
+
const codeChallenge = createHash(codeVerifier, "base64url");
|
|
191
|
+
return { codeVerifier, codeChallenge, method: "S256" };
|
|
192
|
+
};
|
|
208
193
|
var createCSRF = async (jose, csrfCookie) => {
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
return jose.signJWS({ token })
|
|
216
|
-
} catch {
|
|
217
|
-
const token = generateSecure(32)
|
|
218
|
-
return jose.signJWS({ token })
|
|
194
|
+
try {
|
|
195
|
+
const token = generateSecure(32);
|
|
196
|
+
if (csrfCookie) {
|
|
197
|
+
await jose.verifyJWS(csrfCookie);
|
|
198
|
+
return csrfCookie;
|
|
219
199
|
}
|
|
220
|
-
}
|
|
200
|
+
return jose.signJWS({ token });
|
|
201
|
+
} catch {
|
|
202
|
+
const token = generateSecure(32);
|
|
203
|
+
return jose.signJWS({ token });
|
|
204
|
+
}
|
|
205
|
+
};
|
|
221
206
|
var verifyCSRF = async (jose, cookie, header) => {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
233
|
-
return true
|
|
234
|
-
} catch {
|
|
235
|
-
throw new InvalidCsrfTokenError()
|
|
207
|
+
try {
|
|
208
|
+
const { token: cookieToken } = await jose.verifyJWS(cookie);
|
|
209
|
+
const { token: headerToken } = await jose.verifyJWS(header);
|
|
210
|
+
const cookieBuffer = Buffer.from(cookieToken);
|
|
211
|
+
const headerBuffer = Buffer.from(headerToken);
|
|
212
|
+
if (!equals(headerBuffer.length, cookieBuffer.length)) {
|
|
213
|
+
throw new InvalidCsrfTokenError();
|
|
214
|
+
}
|
|
215
|
+
if (!import_node_crypto.default.timingSafeEqual(cookieBuffer, headerBuffer)) {
|
|
216
|
+
throw new InvalidCsrfTokenError();
|
|
236
217
|
}
|
|
237
|
-
|
|
218
|
+
return true;
|
|
219
|
+
} catch {
|
|
220
|
+
throw new InvalidCsrfTokenError();
|
|
221
|
+
}
|
|
222
|
+
};
|
|
238
223
|
var createDerivedSalt = (secret) => {
|
|
239
|
-
|
|
240
|
-
}
|
|
224
|
+
return import_node_crypto.default.createHash("sha256").update(secret).update("aura-auth-salt").digest("hex");
|
|
225
|
+
};
|
|
241
226
|
|
|
242
227
|
// src/jose.ts
|
|
243
228
|
var createJoseInstance = (secret) => {
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
229
|
+
secret ?? (secret = process.env.AURA_AUTH_SECRET);
|
|
230
|
+
if (!secret) {
|
|
231
|
+
throw new AuthError("JOSE_INIT_ERROR", "AURA_AUTH_SECRET environment variable is not set and no secret was provided.");
|
|
232
|
+
}
|
|
233
|
+
const salt = process.env.AURA_AUTH_SALT ?? createDerivedSalt(secret);
|
|
234
|
+
const { derivedKey: derivedSessionKey } = (0, import_jose.createDeriveKey)(secret, salt, "session");
|
|
235
|
+
const { derivedKey: derivedCsrfTokenKey } = (0, import_jose.createDeriveKey)(secret, salt, "csrfToken");
|
|
236
|
+
const { decodeJWT, encodeJWT } = (0, import_jose.createJWT)(derivedSessionKey);
|
|
237
|
+
const { signJWS, verifyJWS } = (0, import_jose.createJWS)(derivedCsrfTokenKey);
|
|
238
|
+
return {
|
|
239
|
+
decodeJWT,
|
|
240
|
+
encodeJWT,
|
|
241
|
+
signJWS,
|
|
242
|
+
verifyJWS
|
|
243
|
+
};
|
|
244
|
+
};
|
|
260
245
|
|
|
261
246
|
// src/cookie.ts
|
|
262
|
-
var import_cookie = require("cookie")
|
|
247
|
+
var import_cookie = require("cookie");
|
|
263
248
|
|
|
264
249
|
// src/assert.ts
|
|
265
250
|
var isRequest = (value) => {
|
|
266
|
-
|
|
267
|
-
}
|
|
251
|
+
return typeof Request !== "undefined" && value instanceof Request;
|
|
252
|
+
};
|
|
268
253
|
var isValidURL = (value) => {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
}
|
|
254
|
+
if (value.includes("\r\n") || value.includes("\n") || value.includes("\r")) return false;
|
|
255
|
+
const regex = /^https?:\/\/(?:[a-zA-Z0-9._-]+|localhost|\[[0-9a-fA-F:]+\])(?::\d{1,5})?(?:\/[a-zA-Z0-9._~!$&'()*+,;=:@-]*)*\/?$/;
|
|
256
|
+
return regex.test(value);
|
|
257
|
+
};
|
|
274
258
|
|
|
275
259
|
// src/cookie.ts
|
|
276
|
-
var import_cookie2 = require("cookie")
|
|
277
|
-
var COOKIE_NAME = "aura-auth"
|
|
260
|
+
var import_cookie2 = require("cookie");
|
|
261
|
+
var COOKIE_NAME = "aura-auth";
|
|
278
262
|
var defaultCookieOptions = {
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
263
|
+
httpOnly: true,
|
|
264
|
+
sameSite: "lax",
|
|
265
|
+
path: "/",
|
|
266
|
+
maxAge: 60 * 60 * 24 * 15
|
|
267
|
+
};
|
|
284
268
|
var defaultCookieConfig = {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
269
|
+
strategy: "standard",
|
|
270
|
+
name: COOKIE_NAME,
|
|
271
|
+
options: defaultCookieOptions
|
|
272
|
+
};
|
|
289
273
|
var defaultStandardCookieConfig = {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}
|
|
274
|
+
secure: false,
|
|
275
|
+
httpOnly: true,
|
|
276
|
+
prefix: ""
|
|
277
|
+
};
|
|
294
278
|
var defaultSecureCookieConfig = {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
}
|
|
279
|
+
secure: true,
|
|
280
|
+
prefix: "__Secure-"
|
|
281
|
+
};
|
|
298
282
|
var defaultHostCookieConfig = {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
}
|
|
283
|
+
secure: true,
|
|
284
|
+
prefix: "__Host-",
|
|
285
|
+
path: "/",
|
|
286
|
+
domain: void 0
|
|
287
|
+
};
|
|
304
288
|
var expiredCookieOptions = {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
}
|
|
289
|
+
...defaultCookieOptions,
|
|
290
|
+
expires: /* @__PURE__ */ new Date(0),
|
|
291
|
+
maxAge: 0
|
|
292
|
+
};
|
|
309
293
|
var defineDefaultCookieOptions = (options2) => {
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
}
|
|
294
|
+
return {
|
|
295
|
+
name: options2?.name ?? COOKIE_NAME,
|
|
296
|
+
prefix: options2?.prefix ?? (options2?.secure ? "__Secure-" : ""),
|
|
297
|
+
...defaultCookieOptions,
|
|
298
|
+
...options2
|
|
299
|
+
};
|
|
300
|
+
};
|
|
317
301
|
var setCookie = (cookieName, value, options2) => {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
}
|
|
302
|
+
const { prefix, name } = defineDefaultCookieOptions(options2);
|
|
303
|
+
const cookieNameWithPrefix = `${prefix}${name}.${cookieName}`;
|
|
304
|
+
return (0, import_cookie.serialize)(cookieNameWithPrefix, value, {
|
|
305
|
+
...defaultCookieOptions,
|
|
306
|
+
...options2
|
|
307
|
+
});
|
|
308
|
+
};
|
|
325
309
|
var getCookie = (petition, cookie, options2, optional = false) => {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
331
|
-
throw new AuthError("invalid_request", "No cookies found. There is no active session")
|
|
310
|
+
const cookies = isRequest(petition) ? petition.headers.get("Cookie") : petition.headers.getSetCookie().join("; ");
|
|
311
|
+
if (!cookies) {
|
|
312
|
+
if (optional) {
|
|
313
|
+
return "";
|
|
332
314
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
315
|
+
throw new AuthError("invalid_request", "No cookies found. There is no active session");
|
|
316
|
+
}
|
|
317
|
+
const { name, prefix } = defineDefaultCookieOptions(options2);
|
|
318
|
+
const parsedCookies = (0, import_cookie.parse)(cookies);
|
|
319
|
+
const value = parsedCookies[`${prefix}${name}.${cookie}`];
|
|
320
|
+
if (value === void 0) {
|
|
321
|
+
if (optional) {
|
|
322
|
+
return "";
|
|
341
323
|
}
|
|
342
|
-
|
|
343
|
-
}
|
|
324
|
+
throw new AuthError("invalid_request", `Cookie "${cookie}" not found. There is no active session`);
|
|
325
|
+
}
|
|
326
|
+
return value;
|
|
327
|
+
};
|
|
344
328
|
var createSessionCookie = async (session, cookieOptions, jose) => {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
}
|
|
329
|
+
try {
|
|
330
|
+
const encoded = await jose.encodeJWT(session);
|
|
331
|
+
return setCookie("sessionToken", encoded, cookieOptions);
|
|
332
|
+
} catch (error) {
|
|
333
|
+
throw new AuthError("server_error", "Failed to create session cookie", { cause: error });
|
|
334
|
+
}
|
|
335
|
+
};
|
|
352
336
|
var secureCookieOptions = (request, cookieOptions, trustedProxyHeaders) => {
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
337
|
+
const name = cookieOptions.name ?? COOKIE_NAME;
|
|
338
|
+
const isSecure = trustedProxyHeaders ? request.url.startsWith("https://") || request.headers.get("X-Forwarded-Proto") === "https" || request.headers.get("Forwarded")?.includes("proto=https") : request.url.startsWith("https://");
|
|
339
|
+
if (!cookieOptions.options?.httpOnly) {
|
|
340
|
+
console.warn(
|
|
341
|
+
"[WARNING]: Cookie is configured without HttpOnly. This allows JavaScript access via document.cookie and increases XSS risk."
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
if (cookieOptions.options?.domain === "*") {
|
|
345
|
+
console.warn("[WARNING]: Cookie 'Domain' is set to '*', which is insecure. Avoid wildcard domains.");
|
|
346
|
+
}
|
|
347
|
+
if (!isSecure) {
|
|
348
|
+
const options2 = cookieOptions.options;
|
|
349
|
+
if (options2?.secure) {
|
|
350
|
+
console.warn(
|
|
351
|
+
"[WARNING]: The 'Secure' attribute will be disabled for this cookie. Serve over HTTPS to enforce Secure cookies."
|
|
352
|
+
);
|
|
363
353
|
}
|
|
364
|
-
if (
|
|
365
|
-
|
|
354
|
+
if (options2?.sameSite == "none") {
|
|
355
|
+
console.warn("[WARNING]: SameSite=None without a secure connection can be blocked by browsers.");
|
|
366
356
|
}
|
|
367
|
-
if (
|
|
368
|
-
|
|
369
|
-
if (options2?.secure) {
|
|
370
|
-
console.warn(
|
|
371
|
-
"[WARNING]: The 'Secure' attribute will be disabled for this cookie. Serve over HTTPS to enforce Secure cookies."
|
|
372
|
-
)
|
|
373
|
-
}
|
|
374
|
-
if (options2?.sameSite == "none") {
|
|
375
|
-
console.warn("[WARNING]: SameSite=None without a secure connection can be blocked by browsers.")
|
|
376
|
-
}
|
|
377
|
-
if (process.env.NODE_ENV === "production") {
|
|
378
|
-
console.warn("[WARNING]: In production, ensure cookies are served over HTTPS to maintain security.")
|
|
379
|
-
}
|
|
380
|
-
return {
|
|
381
|
-
...defaultCookieOptions,
|
|
382
|
-
...cookieOptions.options,
|
|
383
|
-
sameSite: options2?.sameSite === "none" ? "lax" : (options2?.sameSite ?? "lax"),
|
|
384
|
-
...defaultStandardCookieConfig,
|
|
385
|
-
name,
|
|
386
|
-
}
|
|
357
|
+
if (process.env.NODE_ENV === "production") {
|
|
358
|
+
console.warn("[WARNING]: In production, ensure cookies are served over HTTPS to maintain security.");
|
|
387
359
|
}
|
|
388
|
-
return
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
360
|
+
return {
|
|
361
|
+
...defaultCookieOptions,
|
|
362
|
+
...cookieOptions.options,
|
|
363
|
+
sameSite: options2?.sameSite === "none" ? "lax" : options2?.sameSite ?? "lax",
|
|
364
|
+
...defaultStandardCookieConfig,
|
|
365
|
+
name
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
return cookieOptions.strategy === "host" ? {
|
|
369
|
+
...defaultCookieOptions,
|
|
370
|
+
...cookieOptions.options,
|
|
371
|
+
...defaultHostCookieConfig,
|
|
372
|
+
name
|
|
373
|
+
} : { ...defaultCookieOptions, ...cookieOptions.options, ...defaultSecureCookieConfig, name };
|
|
374
|
+
};
|
|
397
375
|
var expireCookie = (name, options2) => {
|
|
398
|
-
|
|
399
|
-
}
|
|
376
|
+
return setCookie(name, "", { ...options2, ...expiredCookieOptions });
|
|
377
|
+
};
|
|
400
378
|
var oauthCookie = (options2) => {
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
}
|
|
379
|
+
return {
|
|
380
|
+
...options2,
|
|
381
|
+
secure: options2.secure,
|
|
382
|
+
httpOnly: options2.httpOnly,
|
|
383
|
+
maxAge: 5 * 60,
|
|
384
|
+
expires: new Date(Date.now() + 5 * 60 * 1e3)
|
|
385
|
+
};
|
|
386
|
+
};
|
|
409
387
|
|
|
410
388
|
// src/oauth/github.ts
|
|
411
389
|
var github = {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
}
|
|
390
|
+
id: "github",
|
|
391
|
+
name: "GitHub",
|
|
392
|
+
authorizeURL: "https://github.com/login/oauth/authorize",
|
|
393
|
+
accessToken: "https://github.com/login/oauth/access_token",
|
|
394
|
+
userInfo: "https://api.github.com/user",
|
|
395
|
+
scope: "read:user user:email",
|
|
396
|
+
responseType: "code"
|
|
397
|
+
};
|
|
420
398
|
|
|
421
399
|
// src/oauth/bitbucket.ts
|
|
422
400
|
var bitbucket = {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
}
|
|
401
|
+
id: "bitbucket",
|
|
402
|
+
name: "Bitbucket",
|
|
403
|
+
authorizeURL: "https://bitbucket.org/site/oauth2/authorize",
|
|
404
|
+
accessToken: "https://bitbucket.org/site/oauth2/access_token",
|
|
405
|
+
userInfo: "https://api.bitbucket.org/2.0/user",
|
|
406
|
+
scope: "account email",
|
|
407
|
+
responseType: "code",
|
|
408
|
+
profile(profile) {
|
|
409
|
+
return {
|
|
410
|
+
sub: profile.uuid ?? profile.account_id,
|
|
411
|
+
name: profile.display_name ?? profile.nickname,
|
|
412
|
+
image: profile.links.avatar.href
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
};
|
|
438
416
|
|
|
439
417
|
// src/oauth/figma.ts
|
|
440
418
|
var figma = {
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
}
|
|
419
|
+
id: "figma",
|
|
420
|
+
name: "Figma",
|
|
421
|
+
authorizeURL: "https://www.figma.com/oauth",
|
|
422
|
+
accessToken: "https://api.figma.com/v1/oauth/token",
|
|
423
|
+
userInfo: "https://api.figma.com/v1/me",
|
|
424
|
+
scope: "current_user:read",
|
|
425
|
+
responseType: "code",
|
|
426
|
+
profile(profile) {
|
|
427
|
+
return {
|
|
428
|
+
sub: profile.id,
|
|
429
|
+
name: profile.handle,
|
|
430
|
+
email: profile.email,
|
|
431
|
+
image: profile.img_url
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
};
|
|
457
435
|
|
|
458
436
|
// src/oauth/discord.ts
|
|
459
437
|
var discord = {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
}
|
|
438
|
+
id: "discord",
|
|
439
|
+
name: "Discord",
|
|
440
|
+
authorizeURL: "https://discord.com/oauth2/authorize",
|
|
441
|
+
accessToken: "https://discord.com/api/oauth2/token",
|
|
442
|
+
userInfo: "https://discord.com/api/users/@me",
|
|
443
|
+
scope: "identify email",
|
|
444
|
+
responseType: "code",
|
|
445
|
+
profile(profile) {
|
|
446
|
+
let image = "";
|
|
447
|
+
if (profile.avatar === null) {
|
|
448
|
+
const index = profile.discriminator === "0" ? (BigInt(profile.id) >> 22n) % 6n : Number(profile.discriminator) % 5;
|
|
449
|
+
image = `https://cdn.discordapp.com/embed/avatars/${index}.png`;
|
|
450
|
+
} else {
|
|
451
|
+
const format = profile.avatar.startsWith("a_") ? "gif" : "png";
|
|
452
|
+
image = `https://cdn.discordapp.com/avatars/${profile.id}/${profile.avatar}.${format}`;
|
|
453
|
+
}
|
|
454
|
+
return {
|
|
455
|
+
sub: profile.id,
|
|
456
|
+
// https://discord.com/developers/docs/change-log#display-names
|
|
457
|
+
name: profile.global_name ?? profile.username,
|
|
458
|
+
email: profile.email ?? "",
|
|
459
|
+
image
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
};
|
|
485
463
|
|
|
486
464
|
// src/oauth/gitlab.ts
|
|
487
465
|
var gitlab = {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
}
|
|
466
|
+
id: "gitlab",
|
|
467
|
+
name: "GitLab",
|
|
468
|
+
authorizeURL: "https://gitlab.com/oauth/authorize",
|
|
469
|
+
accessToken: "https://gitlab.com/oauth/token",
|
|
470
|
+
userInfo: "https://gitlab.com/api/v4/user",
|
|
471
|
+
scope: "read_user",
|
|
472
|
+
responseType: "code",
|
|
473
|
+
profile(profile) {
|
|
474
|
+
return {
|
|
475
|
+
sub: profile.id.toString(),
|
|
476
|
+
name: profile.name ?? profile.username,
|
|
477
|
+
email: profile.email,
|
|
478
|
+
avatar: profile.avatar_url
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
};
|
|
504
482
|
|
|
505
483
|
// src/oauth/spotify.ts
|
|
506
484
|
var spotify = {
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
}
|
|
485
|
+
id: "spotify",
|
|
486
|
+
name: "Spotify",
|
|
487
|
+
authorizeURL: "https://accounts.spotify.com/authorize",
|
|
488
|
+
accessToken: "https://accounts.spotify.com/api/token",
|
|
489
|
+
userInfo: "https://api.spotify.com/v1/me",
|
|
490
|
+
scope: "user-read-email user-read-private",
|
|
491
|
+
responseType: "token",
|
|
492
|
+
profile(profile) {
|
|
493
|
+
return {
|
|
494
|
+
sub: profile.id,
|
|
495
|
+
name: profile.display_name,
|
|
496
|
+
email: profile.email,
|
|
497
|
+
image: profile.images?.[0]?.url
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
};
|
|
523
501
|
|
|
524
502
|
// src/oauth/x.ts
|
|
525
503
|
var x = {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
}
|
|
504
|
+
id: "x",
|
|
505
|
+
name: "X",
|
|
506
|
+
authorizeURL: "https://x.com/i/oauth2/authorize",
|
|
507
|
+
accessToken: "https://api.x.com/2/oauth2/token",
|
|
508
|
+
userInfo: "https://api.x.com/2/users/me?user.fields=profile_image_url",
|
|
509
|
+
scope: "users.read users.email tweet.read offline.access",
|
|
510
|
+
responseType: "code",
|
|
511
|
+
profile({ data }) {
|
|
512
|
+
return {
|
|
513
|
+
sub: data.id,
|
|
514
|
+
name: data.name,
|
|
515
|
+
image: data.profile_image_url,
|
|
516
|
+
email: ""
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
};
|
|
542
520
|
|
|
543
521
|
// src/oauth/index.ts
|
|
544
522
|
var builtInOAuthProviders = {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
}
|
|
523
|
+
github,
|
|
524
|
+
bitbucket,
|
|
525
|
+
figma,
|
|
526
|
+
discord,
|
|
527
|
+
gitlab,
|
|
528
|
+
spotify,
|
|
529
|
+
x
|
|
530
|
+
};
|
|
553
531
|
var defineOAuthEnvironment = (oauth) => {
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
}
|
|
532
|
+
const env = process.env;
|
|
533
|
+
return {
|
|
534
|
+
clientId: env[`AURA_AUTH_${oauth.toUpperCase()}_CLIENT_ID`],
|
|
535
|
+
clientSecret: env[`AURA_AUTH_${oauth.toUpperCase()}_CLIENT_SECRET`]
|
|
536
|
+
};
|
|
537
|
+
};
|
|
560
538
|
var defineOAuthProviderConfig = (config2) => {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
}
|
|
539
|
+
if (typeof config2 === "string") {
|
|
540
|
+
const definition = defineOAuthEnvironment(config2);
|
|
541
|
+
const oauthConfig = builtInOAuthProviders[config2];
|
|
542
|
+
return {
|
|
543
|
+
...oauthConfig,
|
|
544
|
+
...definition
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
return config2;
|
|
548
|
+
};
|
|
571
549
|
var createBuiltInOAuthProviders = (oauth = []) => {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
}
|
|
550
|
+
return oauth.reduce((previous, config2) => {
|
|
551
|
+
const oauthConfig = defineOAuthProviderConfig(config2);
|
|
552
|
+
return { ...previous, [oauthConfig.id]: oauthConfig };
|
|
553
|
+
}, {});
|
|
554
|
+
};
|
|
577
555
|
|
|
578
556
|
// src/actions/signIn/signIn.ts
|
|
579
|
-
var import_zod = __toESM(require("zod"), 1)
|
|
580
|
-
var import_router2 = require("@aura-stack/router")
|
|
557
|
+
var import_zod = __toESM(require("zod"), 1);
|
|
558
|
+
var import_router2 = require("@aura-stack/router");
|
|
581
559
|
|
|
582
560
|
// src/response.ts
|
|
583
561
|
var AuraResponse = class extends Response {
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
}
|
|
562
|
+
static json(body, init) {
|
|
563
|
+
return Response.json(body, init);
|
|
564
|
+
}
|
|
565
|
+
};
|
|
588
566
|
|
|
589
567
|
// src/schemas.ts
|
|
590
|
-
var import_v4 = require("zod/v4")
|
|
568
|
+
var import_v4 = require("zod/v4");
|
|
591
569
|
var OAuthProviderConfigSchema = (0, import_v4.object)({
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
})
|
|
570
|
+
authorizeURL: (0, import_v4.url)(),
|
|
571
|
+
accessToken: (0, import_v4.url)(),
|
|
572
|
+
scope: (0, import_v4.string)().optional(),
|
|
573
|
+
userInfo: (0, import_v4.url)(),
|
|
574
|
+
responseType: (0, import_v4.enum)(["code", "token", "id_token"]),
|
|
575
|
+
clientId: (0, import_v4.string)(),
|
|
576
|
+
clientSecret: (0, import_v4.string)()
|
|
577
|
+
});
|
|
600
578
|
var OAuthAuthorization = OAuthProviderConfigSchema.extend({
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
})
|
|
579
|
+
redirectURI: (0, import_v4.string)(),
|
|
580
|
+
state: (0, import_v4.string)(),
|
|
581
|
+
codeChallenge: (0, import_v4.string)(),
|
|
582
|
+
codeChallengeMethod: (0, import_v4.enum)(["plain", "S256"])
|
|
583
|
+
});
|
|
606
584
|
var OAuthAuthorizationResponse = (0, import_v4.object)({
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
})
|
|
585
|
+
state: (0, import_v4.string)(),
|
|
586
|
+
code: (0, import_v4.string)()
|
|
587
|
+
});
|
|
610
588
|
var OAuthAuthorizationErrorResponse = (0, import_v4.object)({
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
})
|
|
589
|
+
error: (0, import_v4.enum)([
|
|
590
|
+
"invalid_request",
|
|
591
|
+
"unauthorized_client",
|
|
592
|
+
"access_denied",
|
|
593
|
+
"unsupported_response_type",
|
|
594
|
+
"invalid_scope",
|
|
595
|
+
"server_error",
|
|
596
|
+
"temporarily_unavailable"
|
|
597
|
+
]),
|
|
598
|
+
error_description: (0, import_v4.string)().optional(),
|
|
599
|
+
error_uri: (0, import_v4.string)().optional(),
|
|
600
|
+
state: (0, import_v4.string)()
|
|
601
|
+
});
|
|
624
602
|
var OAuthAccessToken = OAuthProviderConfigSchema.extend({
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
})
|
|
603
|
+
redirectURI: (0, import_v4.string)(),
|
|
604
|
+
code: (0, import_v4.string)(),
|
|
605
|
+
codeVerifier: (0, import_v4.string)().min(43).max(128)
|
|
606
|
+
});
|
|
629
607
|
var OAuthAccessTokenResponse = (0, import_v4.object)({
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
})
|
|
608
|
+
access_token: (0, import_v4.string)(),
|
|
609
|
+
token_type: (0, import_v4.string)(),
|
|
610
|
+
expires_in: (0, import_v4.number)().optional(),
|
|
611
|
+
refresh_token: (0, import_v4.string)().optional(),
|
|
612
|
+
scope: (0, import_v4.string)().optional()
|
|
613
|
+
});
|
|
636
614
|
var OAuthAccessTokenErrorResponse = (0, import_v4.object)({
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
})
|
|
615
|
+
error: (0, import_v4.enum)([
|
|
616
|
+
"invalid_request",
|
|
617
|
+
"invalid_client",
|
|
618
|
+
"invalid_grant",
|
|
619
|
+
"unauthorized_client",
|
|
620
|
+
"unsupported_grant_type",
|
|
621
|
+
"invalid_scope"
|
|
622
|
+
]),
|
|
623
|
+
error_description: (0, import_v4.string)().optional(),
|
|
624
|
+
error_uri: (0, import_v4.string)().optional()
|
|
625
|
+
});
|
|
648
626
|
var OAuthErrorResponse = (0, import_v4.object)({
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
})
|
|
627
|
+
error: (0, import_v4.string)(),
|
|
628
|
+
error_description: (0, import_v4.string)().optional()
|
|
629
|
+
});
|
|
652
630
|
|
|
653
631
|
// src/actions/signIn/authorization.ts
|
|
654
632
|
var createAuthorizationURL = (oauthConfig, redirectURI, state, codeChallenge, codeChallengeMethod) => {
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
}
|
|
633
|
+
const parsed = OAuthAuthorization.safeParse({ ...oauthConfig, redirectURI, state, codeChallenge, codeChallengeMethod });
|
|
634
|
+
if (!parsed.success) {
|
|
635
|
+
throw new AuthError(ERROR_RESPONSE.AUTHORIZATION.SERVER_ERROR, "Invalid OAuth configuration");
|
|
636
|
+
}
|
|
637
|
+
const { authorizeURL, ...options2 } = parsed.data;
|
|
638
|
+
const { userInfo, accessToken, clientSecret, ...required } = options2;
|
|
639
|
+
const searchParams = new URLSearchParams(toCastCase(required));
|
|
640
|
+
return `${authorizeURL}?${searchParams}`;
|
|
641
|
+
};
|
|
664
642
|
var getOriginURL = (request, trustedProxyHeaders) => {
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
} else {
|
|
675
|
-
return new URL(getNormalizedOriginPath(request.url))
|
|
676
|
-
}
|
|
677
|
-
}
|
|
643
|
+
const headers = request.headers;
|
|
644
|
+
if (trustedProxyHeaders) {
|
|
645
|
+
const protocol = headers.get("X-Forwarded-Proto") ?? headers.get("Forwarded")?.match(/proto=([^;]+)/i)?.[1] ?? "http";
|
|
646
|
+
const host = headers.get("X-Forwarded-Host") ?? headers.get("Host") ?? headers.get("Forwarded")?.match(/host=([^;]+)/i)?.[1] ?? null;
|
|
647
|
+
return new URL(`${protocol}://${host}${getNormalizedOriginPath(new URL(request.url).pathname)}`);
|
|
648
|
+
} else {
|
|
649
|
+
return new URL(getNormalizedOriginPath(request.url));
|
|
650
|
+
}
|
|
651
|
+
};
|
|
678
652
|
var createRedirectURI = (request, oauth, basePath, trustedProxyHeaders) => {
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
}
|
|
653
|
+
const url2 = getOriginURL(request, trustedProxyHeaders);
|
|
654
|
+
return `${url2.origin}${basePath}/callback/${oauth}`;
|
|
655
|
+
};
|
|
682
656
|
var createRedirectTo = (request, redirectTo, trustedProxyHeaders) => {
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
}
|
|
715
|
-
return "/"
|
|
716
|
-
} catch (error) {
|
|
717
|
-
if (isAuthError(error)) {
|
|
718
|
-
throw error
|
|
719
|
-
}
|
|
720
|
-
throw new AuthError(ERROR_RESPONSE.AUTHORIZATION.INVALID_REQUEST, "Invalid origin (potential CSRF).")
|
|
657
|
+
try {
|
|
658
|
+
const headers = request.headers;
|
|
659
|
+
const origin = headers.get("Origin");
|
|
660
|
+
const referer = headers.get("Referer");
|
|
661
|
+
let hostedURL = getOriginURL(request, trustedProxyHeaders);
|
|
662
|
+
if (redirectTo) {
|
|
663
|
+
if (redirectTo.startsWith("/")) {
|
|
664
|
+
return sanitizeURL(redirectTo);
|
|
665
|
+
}
|
|
666
|
+
const redirectToURL = new URL(sanitizeURL(getNormalizedOriginPath(redirectTo)));
|
|
667
|
+
if (!isValidURL(redirectTo) || !equals(redirectToURL.origin, hostedURL.origin)) {
|
|
668
|
+
throw new InvalidRedirectToError();
|
|
669
|
+
}
|
|
670
|
+
return sanitizeURL(redirectToURL.pathname);
|
|
671
|
+
}
|
|
672
|
+
if (referer) {
|
|
673
|
+
const refererURL = new URL(sanitizeURL(referer));
|
|
674
|
+
if (!isValidURL(referer) || !equals(refererURL.origin, hostedURL.origin)) {
|
|
675
|
+
throw new AuthError(
|
|
676
|
+
ERROR_RESPONSE.AUTHORIZATION.INVALID_REQUEST,
|
|
677
|
+
"The referer of the request does not match the hosted origin."
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
return sanitizeURL(refererURL.pathname);
|
|
681
|
+
}
|
|
682
|
+
if (origin) {
|
|
683
|
+
const originURL = new URL(sanitizeURL(getNormalizedOriginPath(origin)));
|
|
684
|
+
if (!isValidURL(origin) || !equals(originURL.origin, hostedURL.origin)) {
|
|
685
|
+
throw new AuthError(ERROR_RESPONSE.AUTHORIZATION.INVALID_REQUEST, "Invalid origin (potential CSRF).");
|
|
686
|
+
}
|
|
687
|
+
return sanitizeURL(originURL.pathname);
|
|
721
688
|
}
|
|
722
|
-
|
|
689
|
+
return "/";
|
|
690
|
+
} catch (error) {
|
|
691
|
+
if (isAuthError(error)) {
|
|
692
|
+
throw error;
|
|
693
|
+
}
|
|
694
|
+
throw new AuthError(ERROR_RESPONSE.AUTHORIZATION.INVALID_REQUEST, "Invalid origin (potential CSRF).");
|
|
695
|
+
}
|
|
696
|
+
};
|
|
723
697
|
|
|
724
698
|
// src/actions/signIn/signIn.ts
|
|
725
699
|
var signInConfig = (oauth) => {
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
}
|
|
700
|
+
return (0, import_router2.createEndpointConfig)("/signIn/:oauth", {
|
|
701
|
+
schemas: {
|
|
702
|
+
params: import_zod.default.object({
|
|
703
|
+
oauth: import_zod.default.enum(Object.keys(oauth)),
|
|
704
|
+
redirectTo: import_zod.default.string().optional()
|
|
705
|
+
})
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
};
|
|
735
709
|
var signInAction = (oauth) => {
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
}
|
|
710
|
+
return (0, import_router2.createEndpoint)(
|
|
711
|
+
"GET",
|
|
712
|
+
"/signIn/:oauth",
|
|
713
|
+
async (ctx) => {
|
|
714
|
+
const {
|
|
715
|
+
request,
|
|
716
|
+
params: { oauth: oauth2, redirectTo },
|
|
717
|
+
context: { oauth: providers, cookies, trustedProxyHeaders, basePath }
|
|
718
|
+
} = ctx;
|
|
719
|
+
try {
|
|
720
|
+
const cookieOptions = secureCookieOptions(request, cookies, trustedProxyHeaders);
|
|
721
|
+
const state = generateSecure();
|
|
722
|
+
const redirectURI = createRedirectURI(request, oauth2, basePath, trustedProxyHeaders);
|
|
723
|
+
const stateCookie = setCookie("state", state, oauthCookie(cookieOptions));
|
|
724
|
+
const redirectURICookie = setCookie("redirect_uri", redirectURI, oauthCookie(cookieOptions));
|
|
725
|
+
const redirectToCookie = setCookie(
|
|
726
|
+
"redirect_to",
|
|
727
|
+
createRedirectTo(request, redirectTo, trustedProxyHeaders),
|
|
728
|
+
oauthCookie(cookieOptions)
|
|
729
|
+
);
|
|
730
|
+
const { codeVerifier, codeChallenge, method } = await createPKCE();
|
|
731
|
+
const codeVerifierCookie = setCookie("code_verifier", codeVerifier, oauthCookie(cookieOptions));
|
|
732
|
+
const authorization = createAuthorizationURL(providers[oauth2], redirectURI, state, codeChallenge, method);
|
|
733
|
+
const headers = new Headers();
|
|
734
|
+
headers.set("Location", authorization);
|
|
735
|
+
headers.append("Set-Cookie", stateCookie);
|
|
736
|
+
headers.append("Set-Cookie", redirectURICookie);
|
|
737
|
+
headers.append("Set-Cookie", redirectToCookie);
|
|
738
|
+
headers.append("Set-Cookie", codeVerifierCookie);
|
|
739
|
+
return Response.json(
|
|
740
|
+
{ oauth: oauth2 },
|
|
741
|
+
{
|
|
742
|
+
status: 302,
|
|
743
|
+
headers
|
|
744
|
+
}
|
|
745
|
+
);
|
|
746
|
+
} catch (error) {
|
|
747
|
+
if (isAuthError(error)) {
|
|
748
|
+
const { type, message } = error;
|
|
749
|
+
return AuraResponse.json(
|
|
750
|
+
{ error: type, error_description: message },
|
|
751
|
+
{ status: import_router2.statusCode.BAD_REQUEST }
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
return AuraResponse.json(
|
|
755
|
+
{
|
|
756
|
+
error: ERROR_RESPONSE.AUTHORIZATION.SERVER_ERROR,
|
|
757
|
+
error_description: "An unexpected error occurred"
|
|
758
|
+
},
|
|
759
|
+
{ status: import_router2.statusCode.INTERNAL_SERVER_ERROR }
|
|
760
|
+
);
|
|
761
|
+
}
|
|
762
|
+
},
|
|
763
|
+
signInConfig(oauth)
|
|
764
|
+
);
|
|
765
|
+
};
|
|
792
766
|
|
|
793
767
|
// src/actions/callback/callback.ts
|
|
794
|
-
var import_zod2 = __toESM(require("zod"), 1)
|
|
795
|
-
var import_router3 = require("@aura-stack/router")
|
|
768
|
+
var import_zod2 = __toESM(require("zod"), 1);
|
|
769
|
+
var import_router3 = require("@aura-stack/router");
|
|
796
770
|
|
|
797
771
|
// src/headers.ts
|
|
798
772
|
var cacheControl = {
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
}
|
|
773
|
+
"Cache-Control": "no-store",
|
|
774
|
+
Pragma: "no-cache",
|
|
775
|
+
Expires: "0",
|
|
776
|
+
Vary: "Cookie"
|
|
777
|
+
};
|
|
804
778
|
|
|
805
779
|
// src/actions/callback/userinfo.ts
|
|
806
780
|
var getDefaultUserInfo = (profile) => {
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
}
|
|
781
|
+
const sub = generateSecure(16);
|
|
782
|
+
return {
|
|
783
|
+
sub: profile?.id ?? profile?.sub ?? sub,
|
|
784
|
+
email: profile?.email,
|
|
785
|
+
name: profile?.name ?? profile?.username ?? profile?.nickname,
|
|
786
|
+
image: profile?.image ?? profile?.picture
|
|
787
|
+
};
|
|
788
|
+
};
|
|
815
789
|
var getUserInfo = async (oauthConfig, accessToken) => {
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
}
|
|
830
|
-
return oauthConfig?.profile ? oauthConfig.profile(json) : getDefaultUserInfo(json)
|
|
831
|
-
} catch (error) {
|
|
832
|
-
throw throwAuthError(error, "Failed to retrieve userinfo")
|
|
790
|
+
const userinfoEndpoint = oauthConfig.userInfo;
|
|
791
|
+
try {
|
|
792
|
+
const response = await fetch(userinfoEndpoint, {
|
|
793
|
+
method: "GET",
|
|
794
|
+
headers: {
|
|
795
|
+
Accept: "application/json",
|
|
796
|
+
Authorization: `Bearer ${accessToken}`
|
|
797
|
+
}
|
|
798
|
+
});
|
|
799
|
+
const json = await response.json();
|
|
800
|
+
const { success, data } = OAuthErrorResponse.safeParse(json);
|
|
801
|
+
if (success) {
|
|
802
|
+
throw new AuthError(data.error, data?.error_description ?? "An error occurred while fetching user information.");
|
|
833
803
|
}
|
|
834
|
-
|
|
804
|
+
return oauthConfig?.profile ? oauthConfig.profile(json) : getDefaultUserInfo(json);
|
|
805
|
+
} catch (error) {
|
|
806
|
+
throw throwAuthError(error, "Failed to retrieve userinfo");
|
|
807
|
+
}
|
|
808
|
+
};
|
|
835
809
|
|
|
836
810
|
// src/actions/callback/access-token.ts
|
|
837
811
|
var createAccessToken = async (oauthConfig, redirectURI, code, codeVerifier) => {
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
812
|
+
const parsed = OAuthAccessToken.safeParse({ ...oauthConfig, redirectURI, code, codeVerifier });
|
|
813
|
+
if (!parsed.success) {
|
|
814
|
+
throw new AuthError(ERROR_RESPONSE.ACCESS_TOKEN.INVALID_REQUEST, "Invalid OAuth configuration");
|
|
815
|
+
}
|
|
816
|
+
const { accessToken, clientId, clientSecret, code: codeParsed, redirectURI: redirectParsed } = parsed.data;
|
|
817
|
+
try {
|
|
818
|
+
const response = await fetch(accessToken, {
|
|
819
|
+
method: "POST",
|
|
820
|
+
headers: {
|
|
821
|
+
Accept: "application/json",
|
|
822
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
823
|
+
},
|
|
824
|
+
body: new URLSearchParams({
|
|
825
|
+
client_id: clientId,
|
|
826
|
+
client_secret: clientSecret,
|
|
827
|
+
code: codeParsed,
|
|
828
|
+
redirect_uri: redirectParsed,
|
|
829
|
+
grant_type: "authorization_code",
|
|
830
|
+
code_verifier: codeVerifier
|
|
831
|
+
}).toString()
|
|
832
|
+
});
|
|
833
|
+
const json = await response.json();
|
|
834
|
+
const token = OAuthAccessTokenResponse.safeParse(json);
|
|
835
|
+
if (!token.success) {
|
|
836
|
+
const { success, data } = OAuthAccessTokenErrorResponse.safeParse(json);
|
|
837
|
+
if (!success) {
|
|
838
|
+
throw new AuthError(ERROR_RESPONSE.ACCESS_TOKEN.INVALID_GRANT, "Invalid access token response format");
|
|
839
|
+
}
|
|
840
|
+
throw new AuthError(data.error, data?.error_description ?? "Failed to retrieve access token");
|
|
841
841
|
}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
Accept: "application/json",
|
|
848
|
-
"Content-Type": "application/x-www-form-urlencoded",
|
|
849
|
-
},
|
|
850
|
-
body: new URLSearchParams({
|
|
851
|
-
client_id: clientId,
|
|
852
|
-
client_secret: clientSecret,
|
|
853
|
-
code: codeParsed,
|
|
854
|
-
redirect_uri: redirectParsed,
|
|
855
|
-
grant_type: "authorization_code",
|
|
856
|
-
code_verifier: codeVerifier,
|
|
857
|
-
}).toString(),
|
|
858
|
-
})
|
|
859
|
-
const json = await response.json()
|
|
860
|
-
const token = OAuthAccessTokenResponse.safeParse(json)
|
|
861
|
-
if (!token.success) {
|
|
862
|
-
const { success, data } = OAuthAccessTokenErrorResponse.safeParse(json)
|
|
863
|
-
if (!success) {
|
|
864
|
-
throw new AuthError(ERROR_RESPONSE.ACCESS_TOKEN.INVALID_GRANT, "Invalid access token response format")
|
|
865
|
-
}
|
|
866
|
-
throw new AuthError(data.error, data?.error_description ?? "Failed to retrieve access token")
|
|
867
|
-
}
|
|
868
|
-
return token.data
|
|
869
|
-
} catch (error) {
|
|
870
|
-
throw throwAuthError(error, "Failed to create access token")
|
|
871
|
-
}
|
|
872
|
-
}
|
|
842
|
+
return token.data;
|
|
843
|
+
} catch (error) {
|
|
844
|
+
throw throwAuthError(error, "Failed to create access token");
|
|
845
|
+
}
|
|
846
|
+
};
|
|
873
847
|
|
|
874
848
|
// src/actions/callback/callback.ts
|
|
875
849
|
var callbackConfig = (oauth) => {
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
}
|
|
850
|
+
return (0, import_router3.createEndpointConfig)("/callback/:oauth", {
|
|
851
|
+
schemas: {
|
|
852
|
+
searchParams: OAuthAuthorizationResponse,
|
|
853
|
+
params: import_zod2.default.object({
|
|
854
|
+
oauth: import_zod2.default.enum(Object.keys(oauth))
|
|
855
|
+
})
|
|
856
|
+
},
|
|
857
|
+
middlewares: [
|
|
858
|
+
(ctx) => {
|
|
859
|
+
const response = OAuthAuthorizationErrorResponse.safeParse(ctx.searchParams);
|
|
860
|
+
if (response.success) {
|
|
861
|
+
const { error, error_description } = response.data;
|
|
862
|
+
throw new AuthError(error, error_description ?? "OAuth Authorization Error");
|
|
863
|
+
}
|
|
864
|
+
return ctx;
|
|
865
|
+
}
|
|
866
|
+
]
|
|
867
|
+
});
|
|
868
|
+
};
|
|
895
869
|
var callbackAction = (oauth) => {
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
}
|
|
870
|
+
return (0, import_router3.createEndpoint)(
|
|
871
|
+
"GET",
|
|
872
|
+
"/callback/:oauth",
|
|
873
|
+
async (ctx) => {
|
|
874
|
+
const {
|
|
875
|
+
request,
|
|
876
|
+
params: { oauth: oauth2 },
|
|
877
|
+
searchParams: { code, state },
|
|
878
|
+
context: { oauth: providers, cookies, jose, trustedProxyHeaders }
|
|
879
|
+
} = ctx;
|
|
880
|
+
try {
|
|
881
|
+
const oauthConfig = providers[oauth2];
|
|
882
|
+
const cookieOptions = secureCookieOptions(request, cookies, trustedProxyHeaders);
|
|
883
|
+
const cookieState = getCookie(request, "state", cookieOptions);
|
|
884
|
+
const cookieRedirectTo = getCookie(request, "redirect_to", cookieOptions);
|
|
885
|
+
const cookieRedirectURI = getCookie(request, "redirect_uri", cookieOptions);
|
|
886
|
+
const codeVerifier = getCookie(request, "code_verifier", cookieOptions);
|
|
887
|
+
if (!equals(cookieState, state)) {
|
|
888
|
+
throw new AuthError(ERROR_RESPONSE.ACCESS_TOKEN.INVALID_REQUEST, "Mismatching state");
|
|
889
|
+
}
|
|
890
|
+
const accessToken = await createAccessToken(oauthConfig, cookieRedirectURI, code, codeVerifier);
|
|
891
|
+
const sanitized = sanitizeURL(cookieRedirectTo);
|
|
892
|
+
if (!isValidRelativePath(sanitized)) {
|
|
893
|
+
throw new AuthError(
|
|
894
|
+
ERROR_RESPONSE.ACCESS_TOKEN.INVALID_REQUEST,
|
|
895
|
+
"Invalid redirect path. Potential open redirect attack detected."
|
|
896
|
+
);
|
|
897
|
+
}
|
|
898
|
+
const headers = new Headers(cacheControl);
|
|
899
|
+
headers.set("Location", sanitized);
|
|
900
|
+
const userInfo = await getUserInfo(oauthConfig, accessToken.access_token);
|
|
901
|
+
const sessionCookie = await createSessionCookie(userInfo, cookieOptions, jose);
|
|
902
|
+
const csrfToken = await createCSRF(jose);
|
|
903
|
+
const csrfCookie = setCookie(
|
|
904
|
+
"csrfToken",
|
|
905
|
+
csrfToken,
|
|
906
|
+
secureCookieOptions(
|
|
907
|
+
request,
|
|
908
|
+
{
|
|
909
|
+
...cookies,
|
|
910
|
+
strategy: "host"
|
|
911
|
+
},
|
|
912
|
+
trustedProxyHeaders
|
|
913
|
+
)
|
|
914
|
+
);
|
|
915
|
+
headers.set("Set-Cookie", sessionCookie);
|
|
916
|
+
headers.append("Set-Cookie", expireCookie("state", cookieOptions));
|
|
917
|
+
headers.append("Set-Cookie", expireCookie("redirect_uri", cookieOptions));
|
|
918
|
+
headers.append("Set-Cookie", expireCookie("redirect_to", cookieOptions));
|
|
919
|
+
headers.append("Set-Cookie", expireCookie("code_verifier", cookieOptions));
|
|
920
|
+
headers.append("Set-Cookie", csrfCookie);
|
|
921
|
+
return Response.json({ oauth: oauth2 }, { status: 302, headers });
|
|
922
|
+
} catch (error) {
|
|
923
|
+
if (isAuthError(error)) {
|
|
924
|
+
const { type, message } = error;
|
|
925
|
+
return AuraResponse.json(
|
|
926
|
+
{ error: type, error_description: message },
|
|
927
|
+
{ status: import_router3.statusCode.BAD_REQUEST }
|
|
928
|
+
);
|
|
929
|
+
}
|
|
930
|
+
return AuraResponse.json(
|
|
931
|
+
{
|
|
932
|
+
error: ERROR_RESPONSE.ACCESS_TOKEN.INVALID_CLIENT,
|
|
933
|
+
error_description: "An unexpected error occurred"
|
|
934
|
+
},
|
|
935
|
+
{ status: import_router3.statusCode.INTERNAL_SERVER_ERROR }
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
},
|
|
939
|
+
callbackConfig(oauth)
|
|
940
|
+
);
|
|
941
|
+
};
|
|
968
942
|
|
|
969
943
|
// src/actions/session/session.ts
|
|
970
|
-
var import_router4 = require("@aura-stack/router")
|
|
944
|
+
var import_router4 = require("@aura-stack/router");
|
|
971
945
|
var sessionAction = (0, import_router4.createEndpoint)("GET", "/session", async (ctx) => {
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
})
|
|
946
|
+
const {
|
|
947
|
+
request,
|
|
948
|
+
context: { cookies, jose, trustedProxyHeaders }
|
|
949
|
+
} = ctx;
|
|
950
|
+
const cookieOptions = secureCookieOptions(request, cookies, trustedProxyHeaders);
|
|
951
|
+
try {
|
|
952
|
+
const session = getCookie(request, "sessionToken", cookieOptions);
|
|
953
|
+
const decoded = await jose.decodeJWT(session);
|
|
954
|
+
const { exp, iat, jti, nbf, ...user } = decoded;
|
|
955
|
+
const headers = new Headers(cacheControl);
|
|
956
|
+
return Response.json({ user, expires: toISOString(exp * 1e3) }, { headers });
|
|
957
|
+
} catch {
|
|
958
|
+
const headers = new Headers(cacheControl);
|
|
959
|
+
const sessionCookie = expireCookie("sessionToken", cookieOptions);
|
|
960
|
+
headers.set("Set-Cookie", sessionCookie);
|
|
961
|
+
return Response.json({ authenticated: false, message: "Unauthorized" }, { status: 401, headers });
|
|
962
|
+
}
|
|
963
|
+
});
|
|
990
964
|
|
|
991
965
|
// src/actions/signOut/signOut.ts
|
|
992
|
-
var import_zod3 = __toESM(require("zod"), 1)
|
|
993
|
-
var import_router5 = require("@aura-stack/router")
|
|
966
|
+
var import_zod3 = __toESM(require("zod"), 1);
|
|
967
|
+
var import_router5 = require("@aura-stack/router");
|
|
994
968
|
var config = (0, import_router5.createEndpointConfig)({
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
})
|
|
969
|
+
schemas: {
|
|
970
|
+
searchParams: import_zod3.default.object({
|
|
971
|
+
token_type_hint: import_zod3.default.literal("session_token"),
|
|
972
|
+
redirectTo: import_zod3.default.string().optional()
|
|
973
|
+
})
|
|
974
|
+
}
|
|
975
|
+
});
|
|
1002
976
|
var signOutAction = (0, import_router5.createEndpoint)(
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
)
|
|
977
|
+
"POST",
|
|
978
|
+
"/signOut",
|
|
979
|
+
async (ctx) => {
|
|
980
|
+
const {
|
|
981
|
+
request,
|
|
982
|
+
headers,
|
|
983
|
+
searchParams: { redirectTo },
|
|
984
|
+
context: { cookies, jose, trustedProxyHeaders }
|
|
985
|
+
} = ctx;
|
|
986
|
+
try {
|
|
987
|
+
const cookiesOptions = secureCookieOptions(request, cookies, trustedProxyHeaders);
|
|
988
|
+
const session = getCookie(request, "sessionToken", cookiesOptions);
|
|
989
|
+
const csrfToken = getCookie(request, "csrfToken", {
|
|
990
|
+
...cookiesOptions,
|
|
991
|
+
prefix: cookiesOptions.secure ? "__Host-" : ""
|
|
992
|
+
});
|
|
993
|
+
const header = headers.get("X-CSRF-Token");
|
|
994
|
+
if (!header || !session || !csrfToken) {
|
|
995
|
+
throw new Error("Missing CSRF token or session token");
|
|
996
|
+
}
|
|
997
|
+
await verifyCSRF(jose, csrfToken, header);
|
|
998
|
+
await jose.decodeJWT(session);
|
|
999
|
+
const normalizedOriginPath = getNormalizedOriginPath(request.url);
|
|
1000
|
+
const location = createRedirectTo(
|
|
1001
|
+
new Request(normalizedOriginPath, {
|
|
1002
|
+
headers
|
|
1003
|
+
}),
|
|
1004
|
+
redirectTo
|
|
1005
|
+
);
|
|
1006
|
+
const responseHeaders = new Headers(cacheControl);
|
|
1007
|
+
responseHeaders.append("Set-Cookie", expireCookie("sessionToken", cookiesOptions));
|
|
1008
|
+
responseHeaders.append(
|
|
1009
|
+
"Set-Cookie",
|
|
1010
|
+
expireCookie("csrfToken", { ...cookiesOptions, prefix: cookiesOptions.secure ? "__Host-" : "" })
|
|
1011
|
+
);
|
|
1012
|
+
responseHeaders.append("Location", location);
|
|
1013
|
+
return Response.json(
|
|
1014
|
+
{ message: "Signed out successfully" },
|
|
1015
|
+
{ status: import_router5.statusCode.ACCEPTED, headers: responseHeaders }
|
|
1016
|
+
);
|
|
1017
|
+
} catch (error) {
|
|
1018
|
+
if (error instanceof InvalidCsrfTokenError) {
|
|
1019
|
+
return AuraResponse.json(
|
|
1020
|
+
{
|
|
1021
|
+
error: "invalid_csrf_token",
|
|
1022
|
+
error_description: "The provided CSRF token is invalid or has expired"
|
|
1023
|
+
},
|
|
1024
|
+
{ status: import_router5.statusCode.UNAUTHORIZED }
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
1027
|
+
if (error instanceof InvalidRedirectToError) {
|
|
1028
|
+
const { type, message } = error;
|
|
1029
|
+
return AuraResponse.json(
|
|
1030
|
+
{
|
|
1031
|
+
error: type,
|
|
1032
|
+
error_description: message
|
|
1033
|
+
},
|
|
1034
|
+
{ status: import_router5.statusCode.BAD_REQUEST }
|
|
1035
|
+
);
|
|
1036
|
+
}
|
|
1037
|
+
return AuraResponse.json(
|
|
1038
|
+
{
|
|
1039
|
+
error: "invalid_session_token",
|
|
1040
|
+
error_description: "The provided sessionToken is invalid or has already expired"
|
|
1041
|
+
},
|
|
1042
|
+
{ status: import_router5.statusCode.UNAUTHORIZED }
|
|
1043
|
+
);
|
|
1044
|
+
}
|
|
1045
|
+
},
|
|
1046
|
+
config
|
|
1047
|
+
);
|
|
1074
1048
|
|
|
1075
1049
|
// src/actions/csrfToken/csrfToken.ts
|
|
1076
|
-
var import_router6 = require("@aura-stack/router")
|
|
1050
|
+
var import_router6 = require("@aura-stack/router");
|
|
1077
1051
|
var csrfTokenAction = (0, import_router6.createEndpoint)("GET", "/csrfToken", async (ctx) => {
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
})
|
|
1052
|
+
const {
|
|
1053
|
+
request,
|
|
1054
|
+
context: { cookies, jose, trustedProxyHeaders }
|
|
1055
|
+
} = ctx;
|
|
1056
|
+
const cookieOptions = secureCookieOptions(request, { ...cookies, strategy: "host" }, trustedProxyHeaders);
|
|
1057
|
+
const existingCSRFToken = getCookie(request, "csrfToken", cookieOptions, true);
|
|
1058
|
+
const csrfToken = await createCSRF(jose, existingCSRFToken);
|
|
1059
|
+
const headers = new Headers(cacheControl);
|
|
1060
|
+
headers.set("Set-Cookie", setCookie("csrfToken", csrfToken, cookieOptions));
|
|
1061
|
+
return Response.json({ csrfToken }, { headers });
|
|
1062
|
+
});
|
|
1089
1063
|
|
|
1090
1064
|
// src/index.ts
|
|
1091
1065
|
var createInternalConfig = (authConfig) => {
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
},
|
|
1066
|
+
return {
|
|
1067
|
+
basePath: authConfig?.basePath ?? "/auth",
|
|
1068
|
+
onError: onErrorHandler,
|
|
1069
|
+
context: {
|
|
1070
|
+
oauth: createBuiltInOAuthProviders(authConfig?.oauth),
|
|
1071
|
+
cookies: authConfig?.cookies ?? defaultCookieConfig,
|
|
1072
|
+
jose: createJoseInstance(authConfig?.secret),
|
|
1073
|
+
basePath: authConfig?.basePath ?? "/auth",
|
|
1074
|
+
trustedProxyHeaders: !!authConfig?.trustedProxyHeaders
|
|
1102
1075
|
}
|
|
1103
|
-
}
|
|
1076
|
+
};
|
|
1077
|
+
};
|
|
1104
1078
|
var createAuth = (authConfig) => {
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
)
|
|
1116
|
-
return {
|
|
1117
|
-
handlers: router,
|
|
1118
|
-
jose: config2.context.jose,
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1079
|
+
const config2 = createInternalConfig(authConfig);
|
|
1080
|
+
const router = (0, import_router7.createRouter)(
|
|
1081
|
+
[signInAction(config2.context.oauth), callbackAction(config2.context.oauth), sessionAction, signOutAction, csrfTokenAction],
|
|
1082
|
+
config2
|
|
1083
|
+
);
|
|
1084
|
+
return {
|
|
1085
|
+
handlers: router,
|
|
1086
|
+
jose: config2.context.jose
|
|
1087
|
+
};
|
|
1088
|
+
};
|
|
1121
1089
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1122
|
-
0 &&
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
})
|
|
1090
|
+
0 && (module.exports = {
|
|
1091
|
+
createAuth
|
|
1092
|
+
});
|