@shware/security 0.4.8 → 1.0.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/index.cjs +86 -20
- package/dist/auth/index.cjs.map +1 -1
- package/dist/auth/index.d.cts +8 -2
- package/dist/auth/index.d.ts +8 -2
- package/dist/auth/index.mjs +87 -21
- package/dist/auth/index.mjs.map +1 -1
- package/dist/auth/types.cjs.map +1 -1
- package/dist/auth/types.d.cts +10 -3
- package/dist/auth/types.d.ts +10 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/oauth2/client.cjs +42 -8
- package/dist/oauth2/client.cjs.map +1 -1
- package/dist/oauth2/client.d.cts +35 -12
- package/dist/oauth2/client.d.ts +35 -12
- package/dist/oauth2/client.mjs +42 -8
- package/dist/oauth2/client.mjs.map +1 -1
- package/dist/oauth2/error.cjs.map +1 -1
- package/dist/oauth2/error.d.cts +1 -1
- package/dist/oauth2/error.d.ts +1 -1
- package/dist/oauth2/error.mjs.map +1 -1
- package/dist/oauth2/provider/apple.cjs +21 -5
- package/dist/oauth2/provider/apple.cjs.map +1 -1
- package/dist/oauth2/provider/apple.d.cts +12 -4
- package/dist/oauth2/provider/apple.d.ts +12 -4
- package/dist/oauth2/provider/apple.mjs +19 -4
- package/dist/oauth2/provider/apple.mjs.map +1 -1
- package/dist/oauth2/provider/common.cjs +5 -13
- package/dist/oauth2/provider/common.cjs.map +1 -1
- package/dist/oauth2/provider/common.d.cts +6 -11
- package/dist/oauth2/provider/common.d.ts +6 -11
- package/dist/oauth2/provider/common.mjs +5 -13
- package/dist/oauth2/provider/common.mjs.map +1 -1
- package/dist/oauth2/provider/facebook.cjs +19 -3
- package/dist/oauth2/provider/facebook.cjs.map +1 -1
- package/dist/oauth2/provider/facebook.d.cts +17 -3
- package/dist/oauth2/provider/facebook.d.ts +17 -3
- package/dist/oauth2/provider/facebook.mjs +18 -3
- package/dist/oauth2/provider/facebook.mjs.map +1 -1
- package/dist/oauth2/provider/github.cjs +6 -3
- package/dist/oauth2/provider/github.cjs.map +1 -1
- package/dist/oauth2/provider/github.d.cts +11 -3
- package/dist/oauth2/provider/github.d.ts +11 -3
- package/dist/oauth2/provider/github.mjs +5 -3
- package/dist/oauth2/provider/github.mjs.map +1 -1
- package/dist/oauth2/provider/google.cjs +17 -1
- package/dist/oauth2/provider/google.cjs.map +1 -1
- package/dist/oauth2/provider/google.d.cts +11 -4
- package/dist/oauth2/provider/google.d.ts +11 -4
- package/dist/oauth2/provider/google.mjs +16 -1
- package/dist/oauth2/provider/google.mjs.map +1 -1
- package/dist/oauth2/provider/x.cjs +16 -4
- package/dist/oauth2/provider/x.cjs.map +1 -1
- package/dist/oauth2/provider/x.d.cts +11 -3
- package/dist/oauth2/provider/x.d.ts +11 -3
- package/dist/oauth2/provider/x.mjs +15 -4
- package/dist/oauth2/provider/x.mjs.map +1 -1
- package/dist/oauth2/types.cjs +20 -0
- package/dist/oauth2/types.cjs.map +1 -1
- package/dist/oauth2/types.d.cts +73 -12
- package/dist/oauth2/types.d.ts +73 -12
- package/dist/oauth2/types.mjs +13 -0
- package/dist/oauth2/types.mjs.map +1 -1
- package/dist/session/redis-session.cjs +21 -0
- package/dist/session/redis-session.cjs.map +1 -1
- package/dist/session/redis-session.d.cts +4 -0
- package/dist/session/redis-session.d.ts +4 -0
- package/dist/session/redis-session.mjs +21 -0
- package/dist/session/redis-session.mjs.map +1 -1
- package/dist/session/types.cjs.map +1 -1
- package/dist/session/types.d.cts +3 -0
- package/dist/session/types.d.ts +3 -0
- package/dist/utils/http.cjs +2 -2
- package/dist/utils/http.cjs.map +1 -1
- package/dist/utils/http.mjs +2 -2
- package/dist/utils/http.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/oidc/index.cjs +0 -39
- package/dist/oidc/index.cjs.map +0 -1
- package/dist/oidc/index.d.cts +0 -10
- package/dist/oidc/index.d.ts +0 -10
- package/dist/oidc/index.mjs +0 -14
- package/dist/oidc/index.mjs.map +0 -1
package/dist/auth/index.cjs
CHANGED
|
@@ -46,8 +46,10 @@ var Auth = class {
|
|
|
46
46
|
PATH_CSRF = "/csrf";
|
|
47
47
|
PATH_LOGOUT = "/logout";
|
|
48
48
|
PATH_LOGGED = "/logged";
|
|
49
|
+
PATH_OAUTH2_STATE = "/oauth2/state/:registrationId";
|
|
49
50
|
PATH_OAUTH2_AUTHORIZATION = "/oauth2/authorization/:registrationId";
|
|
50
|
-
|
|
51
|
+
PATH_LOGIN_OAUTH2_CODE = "/login/oauth2/code/:registrationId";
|
|
52
|
+
PATH_LOGIN_OAUTH2_NATIVE = "/login/oauth2/native/:registrationId";
|
|
51
53
|
ATTR_OAUTH2_AUTHORIZATION_REQUEST = "oauth2AuthorizationRequest";
|
|
52
54
|
constructor({ repository, oauth2, cookie }) {
|
|
53
55
|
this.repository = repository;
|
|
@@ -83,19 +85,42 @@ var Auth = class {
|
|
|
83
85
|
(0, import_http.setCookie)(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });
|
|
84
86
|
return response;
|
|
85
87
|
};
|
|
88
|
+
createPkceParameters() {
|
|
89
|
+
const code_verifier = (0, import_crypto.randomUUID)();
|
|
90
|
+
const code_challenge = (0, import_crypto.createHash)("sha256").update(code_verifier).digest().toString("base64url");
|
|
91
|
+
return { code_verifier, code_challenge, code_challenge_method: "S256" };
|
|
92
|
+
}
|
|
93
|
+
getOauth2StateKey(state) {
|
|
94
|
+
return `oauth2:state:${state}`;
|
|
95
|
+
}
|
|
96
|
+
oauth2State = async (request) => {
|
|
97
|
+
const { registrationId } = (0, import_http.param)(request, this.PATH_OAUTH2_STATE);
|
|
98
|
+
(0, import_tiny_invariant.default)(this.oauth2Client, "oauth2Client is not initialized");
|
|
99
|
+
const state = (0, import_crypto.randomUUID)();
|
|
100
|
+
const nonce = (0, import_crypto.randomUUID)();
|
|
101
|
+
const pkce = this.createPkceParameters();
|
|
102
|
+
const value = { state, nonce, registrationId, ...pkce };
|
|
103
|
+
await this.repository.setItem(this.getOauth2StateKey(state), value, 10 * 60);
|
|
104
|
+
return Response.json({
|
|
105
|
+
state,
|
|
106
|
+
nonce,
|
|
107
|
+
code_challenge: pkce.code_challenge,
|
|
108
|
+
code_challenge_method: pkce.code_challenge_method
|
|
109
|
+
});
|
|
110
|
+
};
|
|
86
111
|
oauth2Authorization = async (request) => {
|
|
87
112
|
(0, import_tiny_invariant.default)(this.oauth2Client, "oauth2Client is not initialized");
|
|
88
113
|
const { registrationId } = (0, import_http.param)(request, this.PATH_OAUTH2_AUTHORIZATION);
|
|
89
114
|
const state = (0, import_crypto.randomUUID)();
|
|
90
|
-
const
|
|
91
|
-
const uri = this.oauth2Client.createAuthorizationUri(registrationId, state,
|
|
115
|
+
const pkce = this.createPkceParameters();
|
|
116
|
+
const uri = this.oauth2Client.createAuthorizationUri({ registrationId, state, pkce });
|
|
92
117
|
const sessionId = (0, import_http.getCookie)(request, this.cookieName);
|
|
93
118
|
const session = sessionId ? await this.repository.findById(sessionId) ?? this.repository.createSession() : this.repository.createSession();
|
|
94
119
|
const authorizationRequest = {
|
|
95
120
|
state,
|
|
96
|
-
codeVerifier,
|
|
97
121
|
registrationId,
|
|
98
|
-
authorizationRequestUri: uri.href
|
|
122
|
+
authorizationRequestUri: uri.href,
|
|
123
|
+
additionalParameters: pkce
|
|
99
124
|
};
|
|
100
125
|
const value = JSON.stringify(authorizationRequest);
|
|
101
126
|
session.setAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST, value);
|
|
@@ -105,23 +130,26 @@ var Auth = class {
|
|
|
105
130
|
(0, import_http.setCookie)(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });
|
|
106
131
|
return response;
|
|
107
132
|
};
|
|
108
|
-
redirect = (error) => {
|
|
133
|
+
redirect = (error, description) => {
|
|
109
134
|
(0, import_tiny_invariant.default)(this.oauth2Client, "oauth2Client is not initialized");
|
|
110
|
-
|
|
135
|
+
const uri = new URL(this.oauth2Client.errorUri);
|
|
136
|
+
uri.searchParams.set("error", error);
|
|
137
|
+
if (description) uri.searchParams.set("error_description", description);
|
|
138
|
+
return Response.redirect(uri.href, 302);
|
|
111
139
|
};
|
|
112
|
-
|
|
140
|
+
loginOAuth2Code = async (request, onAuthorized) => {
|
|
113
141
|
(0, import_tiny_invariant.default)(this.oauth2Client, "oauth2Client is not initialized");
|
|
114
142
|
const sessionId = (0, import_http.getCookie)(request, this.cookieName);
|
|
115
143
|
const session = sessionId ? await this.repository.findById(sessionId) : null;
|
|
116
144
|
if (!session) {
|
|
117
|
-
return this.redirect("
|
|
145
|
+
return this.redirect("invalid_request", "session not found");
|
|
118
146
|
}
|
|
119
147
|
const json = session.getAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST);
|
|
120
148
|
if (!json) {
|
|
121
|
-
return this.redirect("
|
|
149
|
+
return this.redirect("invalid_request", "authorization request not found");
|
|
122
150
|
}
|
|
123
151
|
const cached = JSON.parse(json);
|
|
124
|
-
const { registrationId } = (0, import_http.param)(request, this.
|
|
152
|
+
const { registrationId } = (0, import_http.param)(request, this.PATH_LOGIN_OAUTH2_CODE);
|
|
125
153
|
let data;
|
|
126
154
|
if (request.method === "POST" && request.headers.get("content-type")?.toLowerCase() === "application/x-www-form-urlencoded") {
|
|
127
155
|
const params = new URLSearchParams(await request.text());
|
|
@@ -131,22 +159,24 @@ var Auth = class {
|
|
|
131
159
|
}
|
|
132
160
|
const parsed = import_client.oauth2RedirectQuerySchema.safeParse(data);
|
|
133
161
|
if (!parsed.success) {
|
|
134
|
-
return this.redirect("
|
|
162
|
+
return this.redirect("invalid_request", "invalid redirect query");
|
|
163
|
+
}
|
|
164
|
+
if (!parsed.data.code || !parsed.data.state) {
|
|
165
|
+
return this.redirect("invalid_request", "invalid redirect query");
|
|
135
166
|
}
|
|
136
167
|
if (parsed.data.state !== cached.state) {
|
|
137
|
-
return this.redirect("
|
|
168
|
+
return this.redirect("invalid_request", "redirect state mismatch");
|
|
138
169
|
}
|
|
139
170
|
if (registrationId !== cached.registrationId) {
|
|
140
|
-
return this.redirect("
|
|
171
|
+
return this.redirect("invalid_request", "redirect registration mismatch");
|
|
141
172
|
}
|
|
142
173
|
const { code } = parsed.data;
|
|
143
|
-
const
|
|
144
|
-
const token = await this.oauth2Client.exchangeAuthorizationCode(
|
|
174
|
+
const token = await this.oauth2Client.exchangeAuthorizationCode({
|
|
145
175
|
registrationId,
|
|
146
176
|
code,
|
|
147
|
-
|
|
148
|
-
);
|
|
149
|
-
const userInfo = await this.oauth2Client.getUserInfo(registrationId, token);
|
|
177
|
+
pkce: cached.additionalParameters
|
|
178
|
+
});
|
|
179
|
+
const userInfo = await this.oauth2Client.getUserInfo({ registrationId, token });
|
|
150
180
|
const principal = await onAuthorized(request, registrationId, userInfo, token);
|
|
151
181
|
session.setLastAccessedTime(Date.now());
|
|
152
182
|
session.removeAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST);
|
|
@@ -154,7 +184,43 @@ var Auth = class {
|
|
|
154
184
|
await this.repository.save(session);
|
|
155
185
|
const response = new Response(null, {
|
|
156
186
|
status: 302,
|
|
157
|
-
headers: { location: this.oauth2Client.
|
|
187
|
+
headers: { location: this.oauth2Client.successUri }
|
|
188
|
+
});
|
|
189
|
+
const maxAge = session.getMaxInactiveInterval();
|
|
190
|
+
(0, import_http.setCookie)(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });
|
|
191
|
+
return response;
|
|
192
|
+
};
|
|
193
|
+
loginOAuth2Native = async (request, onAuthorized) => {
|
|
194
|
+
(0, import_tiny_invariant.default)(this.oauth2Client, "oauth2Client is not initialized");
|
|
195
|
+
const { registrationId } = (0, import_http.param)(request, this.PATH_LOGIN_OAUTH2_NATIVE);
|
|
196
|
+
const credentials = await request.json();
|
|
197
|
+
const key = this.getOauth2StateKey(credentials.state);
|
|
198
|
+
const cached = await this.repository.getItem(key);
|
|
199
|
+
if (!cached) {
|
|
200
|
+
const json = { error: "invalid_request", error_description: "oauth2 state not found" };
|
|
201
|
+
return Response.json(json, { status: 400 });
|
|
202
|
+
}
|
|
203
|
+
if (cached.registrationId !== registrationId) {
|
|
204
|
+
const json = { error: "invalid_request", error_description: "registration mismatch" };
|
|
205
|
+
return Response.json(json, { status: 400 });
|
|
206
|
+
}
|
|
207
|
+
const pkce = {
|
|
208
|
+
code_verifier: cached.code_verifier,
|
|
209
|
+
code_challenge: cached.code_challenge,
|
|
210
|
+
code_challenge_method: cached.code_challenge_method
|
|
211
|
+
};
|
|
212
|
+
const { userInfo, token } = await this.oauth2Client.loginOAuth2Native({
|
|
213
|
+
registrationId,
|
|
214
|
+
credentials,
|
|
215
|
+
pkce
|
|
216
|
+
});
|
|
217
|
+
const principal = await onAuthorized(request, registrationId, userInfo, token);
|
|
218
|
+
const session = this.repository.createSession();
|
|
219
|
+
session.setAttribute(import_session.PRINCIPAL_NAME_INDEX_NAME, principal.name);
|
|
220
|
+
await this.repository.save(session);
|
|
221
|
+
const response = new Response(JSON.stringify(principal), {
|
|
222
|
+
status: 200,
|
|
223
|
+
headers: { "Content-Type": "application/json" }
|
|
158
224
|
});
|
|
159
225
|
const maxAge = session.getMaxInactiveInterval();
|
|
160
226
|
(0, import_http.setCookie)(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });
|
package/dist/auth/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/auth/index.ts"],"sourcesContent":["import { randomUUID } from 'crypto';\nimport invariant from 'tiny-invariant';\nimport { PRINCIPAL_NAME_INDEX_NAME, type SessionRepository } from '../session/index';\nimport {\n param,\n query,\n getCookie,\n setCookie,\n deleteCookie,\n type CookieOptions,\n} from '../utils/http';\nimport { OAuth2Client, oauth2RedirectQuerySchema } from '../oauth2/client';\nimport type { OAuth2AuthorizationRequest } from '../oauth2/types';\nimport type { AuthConfig, AuthService, LoggedHandler, OAuth2AuthorizedHandler } from './types';\n\nexport class Auth implements AuthService {\n private readonly cookieName;\n private readonly cookieOptions: CookieOptions;\n private readonly repository: SessionRepository;\n private readonly oauth2Client: OAuth2Client | null;\n\n public readonly PATH_CSRF = '/csrf' as const;\n public readonly PATH_LOGOUT = '/logout' as const;\n public readonly PATH_LOGGED = '/logged' as const;\n\n public readonly PATH_OAUTH2_AUTHORIZATION = '/oauth2/authorization/:registrationId' as const;\n public readonly PATH_OAUTH2_REDIRECT = '/login/oauth2/code/:registrationId' as const;\n\n public readonly ATTR_OAUTH2_AUTHORIZATION_REQUEST = 'oauth2AuthorizationRequest';\n\n constructor({ repository, oauth2, cookie }: AuthConfig) {\n this.repository = repository;\n const { name, ...cookieOptions } = cookie ?? {};\n this.cookieName = name ?? 'SESSION';\n this.cookieOptions = {\n path: '/',\n sameSite: 'none',\n secure: true,\n httpOnly: true,\n ...cookieOptions,\n };\n this.oauth2Client = oauth2?.client ? new OAuth2Client(oauth2.client) : null;\n }\n\n logout = async (request: Request): Promise<Response> => {\n const sessionId = getCookie(request, this.cookieName);\n if (sessionId) await this.repository.deleteById(sessionId);\n const response = Response.json({});\n deleteCookie(response, this.cookieName);\n return response;\n };\n\n logged = async (request: Request, onLogged?: LoggedHandler): Promise<Response> => {\n // 1. get session from cookie\n const sessionId = getCookie(request, this.cookieName);\n if (!sessionId) return Response.json({ data: false });\n const session = await this.repository.findById(sessionId);\n if (!session) return Response.json({ data: false });\n\n // 2. get principal name from session\n const name = session.getAttribute(PRINCIPAL_NAME_INDEX_NAME);\n session.setLastAccessedTime(Date.now());\n await this.repository.save(session);\n\n // 3. call onLogged\n if (name !== null) await onLogged?.({ name: String(name) });\n\n // 4. renew cookie\n const response = Response.json({ data: name !== null });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n\n return response;\n };\n\n oauth2Authorization = async (request: Request): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const { registrationId } = param(request, this.PATH_OAUTH2_AUTHORIZATION);\n const state = randomUUID();\n const codeVerifier = randomUUID();\n const uri = this.oauth2Client.createAuthorizationUri(registrationId, state, codeVerifier);\n const sessionId = getCookie(request, this.cookieName);\n const session = sessionId\n ? ((await this.repository.findById(sessionId)) ?? this.repository.createSession())\n : this.repository.createSession();\n\n const authorizationRequest: OAuth2AuthorizationRequest = {\n state,\n codeVerifier,\n registrationId,\n authorizationRequestUri: uri.href,\n };\n\n const value = JSON.stringify(authorizationRequest);\n session.setAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST, value);\n await this.repository.save(session);\n\n const response = new Response(null, { status: 302, headers: { location: uri.href } });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n return response;\n };\n\n private redirect = (error: string): Response => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n return Response.redirect(`${this.oauth2Client.errorUri}?error=${error}`, 302);\n };\n\n loginOAuth2 = async (\n request: Request,\n onAuthorized: OAuth2AuthorizedHandler\n ): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n\n // 1. get session from cookie\n const sessionId = getCookie(request, this.cookieName);\n const session = sessionId ? await this.repository.findById(sessionId) : null;\n if (!session) {\n return this.redirect('OAUTH2_AUTHORIZATION_SESSION_NOT_FOUND');\n }\n\n // 2. get cached authorization request from session\n const json = session.getAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST) as string | null;\n if (!json) {\n return this.redirect('OAUTH2_AUTHORIZATION_REQUEST_NOT_FOUND');\n }\n const cached: OAuth2AuthorizationRequest = JSON.parse(json);\n\n // 3. validate redirect query/formdata\n const { registrationId } = param(request, this.PATH_OAUTH2_REDIRECT);\n let data: Record<string, string>;\n // apple redirect: response_mode=form_post\n if (\n request.method === 'POST' &&\n request.headers.get('content-type')?.toLowerCase() === 'application/x-www-form-urlencoded'\n ) {\n const params = new URLSearchParams(await request.text());\n data = Object.fromEntries(params.entries());\n } else {\n data = query(request);\n }\n\n const parsed = oauth2RedirectQuerySchema.safeParse(data);\n if (!parsed.success) {\n return this.redirect('INVALID_OAUTH2_REDIRECT_QUERY');\n }\n if (parsed.data.state !== cached.state) {\n return this.redirect('INVALID_OAUTH2_REDIRECT_STATE');\n }\n if (registrationId !== cached.registrationId) {\n return this.redirect('INVALID_OAUTH2_REDIRECT_REGISTRATION_ID');\n }\n\n // 4. exchange authorization code for token and get user info\n const { code } = parsed.data;\n const { codeVerifier } = cached;\n const token = await this.oauth2Client.exchangeAuthorizationCode(\n registrationId,\n code,\n codeVerifier\n );\n const userInfo = await this.oauth2Client.getUserInfo(registrationId, token);\n\n // 5. create or update principal\n const principal = await onAuthorized(request, registrationId, userInfo, token);\n\n // 6. update session\n session.setLastAccessedTime(Date.now());\n session.removeAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST);\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n\n const response = new Response(null, {\n status: 302,\n headers: { location: this.oauth2Client.baseUri },\n });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n return response;\n };\n}\n"],"mappings":"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","names":["invariant"]}
|
|
1
|
+
{"version":3,"sources":["../../src/auth/index.ts"],"sourcesContent":["import { createHash, randomUUID } from 'crypto';\nimport invariant from 'tiny-invariant';\nimport { PRINCIPAL_NAME_INDEX_NAME, type SessionRepository } from '../session/index';\nimport {\n param,\n query,\n getCookie,\n setCookie,\n deleteCookie,\n type CookieOptions,\n} from '../utils/http';\nimport { OAuth2Client, oauth2RedirectQuerySchema } from '../oauth2/client';\nimport { OAuth2ErrorType } from '../oauth2/error';\nimport type {\n NativeCredentials,\n OAuth2AuthorizationRequest,\n PkceParameters,\n} from '../oauth2/types';\nimport type {\n AuthConfig,\n AuthService,\n LoggedHandler,\n OAuth2AuthorizedHandler,\n OAuth2State,\n} from './types';\n\nexport class Auth implements AuthService {\n private readonly cookieName;\n private readonly cookieOptions: CookieOptions;\n private readonly repository: SessionRepository;\n private readonly oauth2Client: OAuth2Client | null;\n\n public readonly PATH_CSRF = '/csrf' as const;\n public readonly PATH_LOGOUT = '/logout' as const;\n public readonly PATH_LOGGED = '/logged' as const;\n\n public readonly PATH_OAUTH2_STATE = '/oauth2/state/:registrationId' as const;\n public readonly PATH_OAUTH2_AUTHORIZATION = '/oauth2/authorization/:registrationId' as const;\n public readonly PATH_LOGIN_OAUTH2_CODE = '/login/oauth2/code/:registrationId' as const;\n public readonly PATH_LOGIN_OAUTH2_NATIVE = '/login/oauth2/native/:registrationId' as const;\n\n public readonly ATTR_OAUTH2_AUTHORIZATION_REQUEST = 'oauth2AuthorizationRequest';\n\n constructor({ repository, oauth2, cookie }: AuthConfig) {\n this.repository = repository;\n const { name, ...cookieOptions } = cookie ?? {};\n this.cookieName = name ?? 'SESSION';\n this.cookieOptions = {\n path: '/',\n sameSite: 'none',\n secure: true,\n httpOnly: true,\n ...cookieOptions,\n };\n this.oauth2Client = oauth2?.client ? new OAuth2Client(oauth2.client) : null;\n }\n\n logout = async (request: Request): Promise<Response> => {\n const sessionId = getCookie(request, this.cookieName);\n if (sessionId) await this.repository.deleteById(sessionId);\n const response = Response.json({});\n deleteCookie(response, this.cookieName);\n return response;\n };\n\n logged = async (request: Request, onLogged?: LoggedHandler): Promise<Response> => {\n // 1. get session from cookie\n const sessionId = getCookie(request, this.cookieName);\n if (!sessionId) return Response.json({ data: false });\n const session = await this.repository.findById(sessionId);\n if (!session) return Response.json({ data: false });\n\n // 2. get principal name from session\n const name = session.getAttribute(PRINCIPAL_NAME_INDEX_NAME);\n session.setLastAccessedTime(Date.now());\n await this.repository.save(session);\n\n // 3. call onLogged\n if (name !== null) await onLogged?.({ name: String(name) });\n\n // 4. renew cookie\n const response = Response.json({ data: name !== null });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n\n return response;\n };\n\n private createPkceParameters(): PkceParameters {\n const code_verifier = randomUUID();\n const code_challenge = createHash('sha256')\n .update(code_verifier)\n .digest()\n .toString('base64url');\n\n return { code_verifier, code_challenge, code_challenge_method: 'S256' };\n }\n\n private getOauth2StateKey(state: string) {\n return `oauth2:state:${state}`;\n }\n\n oauth2State = async (request: Request): Promise<Response> => {\n const { registrationId } = param(request, this.PATH_OAUTH2_STATE);\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const state = randomUUID();\n const nonce = randomUUID();\n const pkce = this.createPkceParameters();\n const value: OAuth2State = { state, nonce, registrationId, ...pkce };\n await this.repository.setItem(this.getOauth2StateKey(state), value, 10 * 60);\n return Response.json({\n state,\n nonce,\n code_challenge: pkce.code_challenge,\n code_challenge_method: pkce.code_challenge_method,\n });\n };\n\n oauth2Authorization = async (request: Request): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const { registrationId } = param(request, this.PATH_OAUTH2_AUTHORIZATION);\n const state = randomUUID();\n const pkce = this.createPkceParameters();\n const uri = this.oauth2Client.createAuthorizationUri({ registrationId, state, pkce });\n const sessionId = getCookie(request, this.cookieName);\n const session = sessionId\n ? ((await this.repository.findById(sessionId)) ?? this.repository.createSession())\n : this.repository.createSession();\n\n const authorizationRequest: OAuth2AuthorizationRequest = {\n state,\n registrationId,\n authorizationRequestUri: uri.href,\n additionalParameters: pkce,\n };\n\n const value = JSON.stringify(authorizationRequest);\n session.setAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST, value);\n await this.repository.save(session);\n\n const response = new Response(null, { status: 302, headers: { location: uri.href } });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n return response;\n };\n\n private redirect = (error: OAuth2ErrorType, description?: string): Response => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const uri = new URL(this.oauth2Client.errorUri);\n uri.searchParams.set('error', error);\n if (description) uri.searchParams.set('error_description', description);\n return Response.redirect(uri.href, 302);\n };\n\n loginOAuth2Code = async (\n request: Request,\n onAuthorized: OAuth2AuthorizedHandler\n ): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n\n // 1. get session from cookie\n const sessionId = getCookie(request, this.cookieName);\n const session = sessionId ? await this.repository.findById(sessionId) : null;\n if (!session) {\n return this.redirect('invalid_request', 'session not found');\n }\n\n // 2. get cached authorization request from session\n const json = session.getAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST) as string | null;\n if (!json) {\n return this.redirect('invalid_request', 'authorization request not found');\n }\n const cached: OAuth2AuthorizationRequest = JSON.parse(json);\n\n // 3. validate redirect query/formdata\n const { registrationId } = param(request, this.PATH_LOGIN_OAUTH2_CODE);\n let data: Record<string, string>;\n // apple redirect: response_mode=form_post\n if (\n request.method === 'POST' &&\n request.headers.get('content-type')?.toLowerCase() === 'application/x-www-form-urlencoded'\n ) {\n const params = new URLSearchParams(await request.text());\n data = Object.fromEntries(params.entries());\n } else {\n data = query(request);\n }\n\n const parsed = oauth2RedirectQuerySchema.safeParse(data);\n if (!parsed.success) {\n return this.redirect('invalid_request', 'invalid redirect query');\n }\n if (!parsed.data.code || !parsed.data.state) {\n return this.redirect('invalid_request', 'invalid redirect query');\n }\n if (parsed.data.state !== cached.state) {\n return this.redirect('invalid_request', 'redirect state mismatch');\n }\n if (registrationId !== cached.registrationId) {\n return this.redirect('invalid_request', 'redirect registration mismatch');\n }\n\n // 4. exchange authorization code for token and get user info\n const { code } = parsed.data;\n\n const token = await this.oauth2Client.exchangeAuthorizationCode({\n registrationId,\n code,\n pkce: cached.additionalParameters,\n });\n const userInfo = await this.oauth2Client.getUserInfo({ registrationId, token });\n\n // 5. create or update principal\n const principal = await onAuthorized(request, registrationId, userInfo, token);\n\n // 6. update session\n session.setLastAccessedTime(Date.now());\n session.removeAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST);\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n\n const response = new Response(null, {\n status: 302,\n headers: { location: this.oauth2Client.successUri },\n });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n return response;\n };\n\n loginOAuth2Native = async (\n request: Request,\n onAuthorized: OAuth2AuthorizedHandler\n ): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const { registrationId } = param(request, this.PATH_LOGIN_OAUTH2_NATIVE);\n const credentials = (await request.json()) as NativeCredentials;\n const key = this.getOauth2StateKey(credentials.state);\n const cached = await this.repository.getItem<OAuth2State>(key);\n if (!cached) {\n const json = { error: 'invalid_request', error_description: 'oauth2 state not found' };\n return Response.json(json, { status: 400 });\n }\n if (cached.registrationId !== registrationId) {\n const json = { error: 'invalid_request', error_description: 'registration mismatch' };\n return Response.json(json, { status: 400 });\n }\n const pkce = {\n code_verifier: cached.code_verifier,\n code_challenge: cached.code_challenge,\n code_challenge_method: cached.code_challenge_method,\n };\n\n const { userInfo, token } = await this.oauth2Client.loginOAuth2Native({\n registrationId,\n credentials,\n pkce,\n });\n const principal = await onAuthorized(request, registrationId, userInfo, token);\n const session = this.repository.createSession();\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n const response = new Response(JSON.stringify(principal), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n return response;\n };\n}\n"],"mappings":"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","names":["invariant"]}
|
package/dist/auth/index.d.cts
CHANGED
|
@@ -13,15 +13,21 @@ declare class Auth implements AuthService {
|
|
|
13
13
|
readonly PATH_CSRF: "/csrf";
|
|
14
14
|
readonly PATH_LOGOUT: "/logout";
|
|
15
15
|
readonly PATH_LOGGED: "/logged";
|
|
16
|
+
readonly PATH_OAUTH2_STATE: "/oauth2/state/:registrationId";
|
|
16
17
|
readonly PATH_OAUTH2_AUTHORIZATION: "/oauth2/authorization/:registrationId";
|
|
17
|
-
readonly
|
|
18
|
+
readonly PATH_LOGIN_OAUTH2_CODE: "/login/oauth2/code/:registrationId";
|
|
19
|
+
readonly PATH_LOGIN_OAUTH2_NATIVE: "/login/oauth2/native/:registrationId";
|
|
18
20
|
readonly ATTR_OAUTH2_AUTHORIZATION_REQUEST = "oauth2AuthorizationRequest";
|
|
19
21
|
constructor({ repository, oauth2, cookie }: AuthConfig);
|
|
20
22
|
logout: (request: Request) => Promise<Response>;
|
|
21
23
|
logged: (request: Request, onLogged?: LoggedHandler) => Promise<Response>;
|
|
24
|
+
private createPkceParameters;
|
|
25
|
+
private getOauth2StateKey;
|
|
26
|
+
oauth2State: (request: Request) => Promise<Response>;
|
|
22
27
|
oauth2Authorization: (request: Request) => Promise<Response>;
|
|
23
28
|
private redirect;
|
|
24
|
-
|
|
29
|
+
loginOAuth2Code: (request: Request, onAuthorized: OAuth2AuthorizedHandler) => Promise<Response>;
|
|
30
|
+
loginOAuth2Native: (request: Request, onAuthorized: OAuth2AuthorizedHandler) => Promise<Response>;
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
export { Auth };
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -13,15 +13,21 @@ declare class Auth implements AuthService {
|
|
|
13
13
|
readonly PATH_CSRF: "/csrf";
|
|
14
14
|
readonly PATH_LOGOUT: "/logout";
|
|
15
15
|
readonly PATH_LOGGED: "/logged";
|
|
16
|
+
readonly PATH_OAUTH2_STATE: "/oauth2/state/:registrationId";
|
|
16
17
|
readonly PATH_OAUTH2_AUTHORIZATION: "/oauth2/authorization/:registrationId";
|
|
17
|
-
readonly
|
|
18
|
+
readonly PATH_LOGIN_OAUTH2_CODE: "/login/oauth2/code/:registrationId";
|
|
19
|
+
readonly PATH_LOGIN_OAUTH2_NATIVE: "/login/oauth2/native/:registrationId";
|
|
18
20
|
readonly ATTR_OAUTH2_AUTHORIZATION_REQUEST = "oauth2AuthorizationRequest";
|
|
19
21
|
constructor({ repository, oauth2, cookie }: AuthConfig);
|
|
20
22
|
logout: (request: Request) => Promise<Response>;
|
|
21
23
|
logged: (request: Request, onLogged?: LoggedHandler) => Promise<Response>;
|
|
24
|
+
private createPkceParameters;
|
|
25
|
+
private getOauth2StateKey;
|
|
26
|
+
oauth2State: (request: Request) => Promise<Response>;
|
|
22
27
|
oauth2Authorization: (request: Request) => Promise<Response>;
|
|
23
28
|
private redirect;
|
|
24
|
-
|
|
29
|
+
loginOAuth2Code: (request: Request, onAuthorized: OAuth2AuthorizedHandler) => Promise<Response>;
|
|
30
|
+
loginOAuth2Native: (request: Request, onAuthorized: OAuth2AuthorizedHandler) => Promise<Response>;
|
|
25
31
|
}
|
|
26
32
|
|
|
27
33
|
export { Auth };
|
package/dist/auth/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/auth/index.ts
|
|
2
|
-
import { randomUUID } from "crypto";
|
|
2
|
+
import { createHash, randomUUID } from "crypto";
|
|
3
3
|
import invariant from "tiny-invariant";
|
|
4
4
|
import { PRINCIPAL_NAME_INDEX_NAME } from "../session/index.mjs";
|
|
5
5
|
import {
|
|
@@ -18,8 +18,10 @@ var Auth = class {
|
|
|
18
18
|
PATH_CSRF = "/csrf";
|
|
19
19
|
PATH_LOGOUT = "/logout";
|
|
20
20
|
PATH_LOGGED = "/logged";
|
|
21
|
+
PATH_OAUTH2_STATE = "/oauth2/state/:registrationId";
|
|
21
22
|
PATH_OAUTH2_AUTHORIZATION = "/oauth2/authorization/:registrationId";
|
|
22
|
-
|
|
23
|
+
PATH_LOGIN_OAUTH2_CODE = "/login/oauth2/code/:registrationId";
|
|
24
|
+
PATH_LOGIN_OAUTH2_NATIVE = "/login/oauth2/native/:registrationId";
|
|
23
25
|
ATTR_OAUTH2_AUTHORIZATION_REQUEST = "oauth2AuthorizationRequest";
|
|
24
26
|
constructor({ repository, oauth2, cookie }) {
|
|
25
27
|
this.repository = repository;
|
|
@@ -55,19 +57,42 @@ var Auth = class {
|
|
|
55
57
|
setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });
|
|
56
58
|
return response;
|
|
57
59
|
};
|
|
60
|
+
createPkceParameters() {
|
|
61
|
+
const code_verifier = randomUUID();
|
|
62
|
+
const code_challenge = createHash("sha256").update(code_verifier).digest().toString("base64url");
|
|
63
|
+
return { code_verifier, code_challenge, code_challenge_method: "S256" };
|
|
64
|
+
}
|
|
65
|
+
getOauth2StateKey(state) {
|
|
66
|
+
return `oauth2:state:${state}`;
|
|
67
|
+
}
|
|
68
|
+
oauth2State = async (request) => {
|
|
69
|
+
const { registrationId } = param(request, this.PATH_OAUTH2_STATE);
|
|
70
|
+
invariant(this.oauth2Client, "oauth2Client is not initialized");
|
|
71
|
+
const state = randomUUID();
|
|
72
|
+
const nonce = randomUUID();
|
|
73
|
+
const pkce = this.createPkceParameters();
|
|
74
|
+
const value = { state, nonce, registrationId, ...pkce };
|
|
75
|
+
await this.repository.setItem(this.getOauth2StateKey(state), value, 10 * 60);
|
|
76
|
+
return Response.json({
|
|
77
|
+
state,
|
|
78
|
+
nonce,
|
|
79
|
+
code_challenge: pkce.code_challenge,
|
|
80
|
+
code_challenge_method: pkce.code_challenge_method
|
|
81
|
+
});
|
|
82
|
+
};
|
|
58
83
|
oauth2Authorization = async (request) => {
|
|
59
84
|
invariant(this.oauth2Client, "oauth2Client is not initialized");
|
|
60
85
|
const { registrationId } = param(request, this.PATH_OAUTH2_AUTHORIZATION);
|
|
61
86
|
const state = randomUUID();
|
|
62
|
-
const
|
|
63
|
-
const uri = this.oauth2Client.createAuthorizationUri(registrationId, state,
|
|
87
|
+
const pkce = this.createPkceParameters();
|
|
88
|
+
const uri = this.oauth2Client.createAuthorizationUri({ registrationId, state, pkce });
|
|
64
89
|
const sessionId = getCookie(request, this.cookieName);
|
|
65
90
|
const session = sessionId ? await this.repository.findById(sessionId) ?? this.repository.createSession() : this.repository.createSession();
|
|
66
91
|
const authorizationRequest = {
|
|
67
92
|
state,
|
|
68
|
-
codeVerifier,
|
|
69
93
|
registrationId,
|
|
70
|
-
authorizationRequestUri: uri.href
|
|
94
|
+
authorizationRequestUri: uri.href,
|
|
95
|
+
additionalParameters: pkce
|
|
71
96
|
};
|
|
72
97
|
const value = JSON.stringify(authorizationRequest);
|
|
73
98
|
session.setAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST, value);
|
|
@@ -77,23 +102,26 @@ var Auth = class {
|
|
|
77
102
|
setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });
|
|
78
103
|
return response;
|
|
79
104
|
};
|
|
80
|
-
redirect = (error) => {
|
|
105
|
+
redirect = (error, description) => {
|
|
81
106
|
invariant(this.oauth2Client, "oauth2Client is not initialized");
|
|
82
|
-
|
|
107
|
+
const uri = new URL(this.oauth2Client.errorUri);
|
|
108
|
+
uri.searchParams.set("error", error);
|
|
109
|
+
if (description) uri.searchParams.set("error_description", description);
|
|
110
|
+
return Response.redirect(uri.href, 302);
|
|
83
111
|
};
|
|
84
|
-
|
|
112
|
+
loginOAuth2Code = async (request, onAuthorized) => {
|
|
85
113
|
invariant(this.oauth2Client, "oauth2Client is not initialized");
|
|
86
114
|
const sessionId = getCookie(request, this.cookieName);
|
|
87
115
|
const session = sessionId ? await this.repository.findById(sessionId) : null;
|
|
88
116
|
if (!session) {
|
|
89
|
-
return this.redirect("
|
|
117
|
+
return this.redirect("invalid_request", "session not found");
|
|
90
118
|
}
|
|
91
119
|
const json = session.getAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST);
|
|
92
120
|
if (!json) {
|
|
93
|
-
return this.redirect("
|
|
121
|
+
return this.redirect("invalid_request", "authorization request not found");
|
|
94
122
|
}
|
|
95
123
|
const cached = JSON.parse(json);
|
|
96
|
-
const { registrationId } = param(request, this.
|
|
124
|
+
const { registrationId } = param(request, this.PATH_LOGIN_OAUTH2_CODE);
|
|
97
125
|
let data;
|
|
98
126
|
if (request.method === "POST" && request.headers.get("content-type")?.toLowerCase() === "application/x-www-form-urlencoded") {
|
|
99
127
|
const params = new URLSearchParams(await request.text());
|
|
@@ -103,22 +131,24 @@ var Auth = class {
|
|
|
103
131
|
}
|
|
104
132
|
const parsed = oauth2RedirectQuerySchema.safeParse(data);
|
|
105
133
|
if (!parsed.success) {
|
|
106
|
-
return this.redirect("
|
|
134
|
+
return this.redirect("invalid_request", "invalid redirect query");
|
|
135
|
+
}
|
|
136
|
+
if (!parsed.data.code || !parsed.data.state) {
|
|
137
|
+
return this.redirect("invalid_request", "invalid redirect query");
|
|
107
138
|
}
|
|
108
139
|
if (parsed.data.state !== cached.state) {
|
|
109
|
-
return this.redirect("
|
|
140
|
+
return this.redirect("invalid_request", "redirect state mismatch");
|
|
110
141
|
}
|
|
111
142
|
if (registrationId !== cached.registrationId) {
|
|
112
|
-
return this.redirect("
|
|
143
|
+
return this.redirect("invalid_request", "redirect registration mismatch");
|
|
113
144
|
}
|
|
114
145
|
const { code } = parsed.data;
|
|
115
|
-
const
|
|
116
|
-
const token = await this.oauth2Client.exchangeAuthorizationCode(
|
|
146
|
+
const token = await this.oauth2Client.exchangeAuthorizationCode({
|
|
117
147
|
registrationId,
|
|
118
148
|
code,
|
|
119
|
-
|
|
120
|
-
);
|
|
121
|
-
const userInfo = await this.oauth2Client.getUserInfo(registrationId, token);
|
|
149
|
+
pkce: cached.additionalParameters
|
|
150
|
+
});
|
|
151
|
+
const userInfo = await this.oauth2Client.getUserInfo({ registrationId, token });
|
|
122
152
|
const principal = await onAuthorized(request, registrationId, userInfo, token);
|
|
123
153
|
session.setLastAccessedTime(Date.now());
|
|
124
154
|
session.removeAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST);
|
|
@@ -126,7 +156,43 @@ var Auth = class {
|
|
|
126
156
|
await this.repository.save(session);
|
|
127
157
|
const response = new Response(null, {
|
|
128
158
|
status: 302,
|
|
129
|
-
headers: { location: this.oauth2Client.
|
|
159
|
+
headers: { location: this.oauth2Client.successUri }
|
|
160
|
+
});
|
|
161
|
+
const maxAge = session.getMaxInactiveInterval();
|
|
162
|
+
setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });
|
|
163
|
+
return response;
|
|
164
|
+
};
|
|
165
|
+
loginOAuth2Native = async (request, onAuthorized) => {
|
|
166
|
+
invariant(this.oauth2Client, "oauth2Client is not initialized");
|
|
167
|
+
const { registrationId } = param(request, this.PATH_LOGIN_OAUTH2_NATIVE);
|
|
168
|
+
const credentials = await request.json();
|
|
169
|
+
const key = this.getOauth2StateKey(credentials.state);
|
|
170
|
+
const cached = await this.repository.getItem(key);
|
|
171
|
+
if (!cached) {
|
|
172
|
+
const json = { error: "invalid_request", error_description: "oauth2 state not found" };
|
|
173
|
+
return Response.json(json, { status: 400 });
|
|
174
|
+
}
|
|
175
|
+
if (cached.registrationId !== registrationId) {
|
|
176
|
+
const json = { error: "invalid_request", error_description: "registration mismatch" };
|
|
177
|
+
return Response.json(json, { status: 400 });
|
|
178
|
+
}
|
|
179
|
+
const pkce = {
|
|
180
|
+
code_verifier: cached.code_verifier,
|
|
181
|
+
code_challenge: cached.code_challenge,
|
|
182
|
+
code_challenge_method: cached.code_challenge_method
|
|
183
|
+
};
|
|
184
|
+
const { userInfo, token } = await this.oauth2Client.loginOAuth2Native({
|
|
185
|
+
registrationId,
|
|
186
|
+
credentials,
|
|
187
|
+
pkce
|
|
188
|
+
});
|
|
189
|
+
const principal = await onAuthorized(request, registrationId, userInfo, token);
|
|
190
|
+
const session = this.repository.createSession();
|
|
191
|
+
session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);
|
|
192
|
+
await this.repository.save(session);
|
|
193
|
+
const response = new Response(JSON.stringify(principal), {
|
|
194
|
+
status: 200,
|
|
195
|
+
headers: { "Content-Type": "application/json" }
|
|
130
196
|
});
|
|
131
197
|
const maxAge = session.getMaxInactiveInterval();
|
|
132
198
|
setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });
|
package/dist/auth/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/auth/index.ts"],"sourcesContent":["import { randomUUID } from 'crypto';\nimport invariant from 'tiny-invariant';\nimport { PRINCIPAL_NAME_INDEX_NAME, type SessionRepository } from '../session/index';\nimport {\n param,\n query,\n getCookie,\n setCookie,\n deleteCookie,\n type CookieOptions,\n} from '../utils/http';\nimport { OAuth2Client, oauth2RedirectQuerySchema } from '../oauth2/client';\nimport type { OAuth2AuthorizationRequest } from '../oauth2/types';\nimport type { AuthConfig, AuthService, LoggedHandler, OAuth2AuthorizedHandler } from './types';\n\nexport class Auth implements AuthService {\n private readonly cookieName;\n private readonly cookieOptions: CookieOptions;\n private readonly repository: SessionRepository;\n private readonly oauth2Client: OAuth2Client | null;\n\n public readonly PATH_CSRF = '/csrf' as const;\n public readonly PATH_LOGOUT = '/logout' as const;\n public readonly PATH_LOGGED = '/logged' as const;\n\n public readonly PATH_OAUTH2_AUTHORIZATION = '/oauth2/authorization/:registrationId' as const;\n public readonly PATH_OAUTH2_REDIRECT = '/login/oauth2/code/:registrationId' as const;\n\n public readonly ATTR_OAUTH2_AUTHORIZATION_REQUEST = 'oauth2AuthorizationRequest';\n\n constructor({ repository, oauth2, cookie }: AuthConfig) {\n this.repository = repository;\n const { name, ...cookieOptions } = cookie ?? {};\n this.cookieName = name ?? 'SESSION';\n this.cookieOptions = {\n path: '/',\n sameSite: 'none',\n secure: true,\n httpOnly: true,\n ...cookieOptions,\n };\n this.oauth2Client = oauth2?.client ? new OAuth2Client(oauth2.client) : null;\n }\n\n logout = async (request: Request): Promise<Response> => {\n const sessionId = getCookie(request, this.cookieName);\n if (sessionId) await this.repository.deleteById(sessionId);\n const response = Response.json({});\n deleteCookie(response, this.cookieName);\n return response;\n };\n\n logged = async (request: Request, onLogged?: LoggedHandler): Promise<Response> => {\n // 1. get session from cookie\n const sessionId = getCookie(request, this.cookieName);\n if (!sessionId) return Response.json({ data: false });\n const session = await this.repository.findById(sessionId);\n if (!session) return Response.json({ data: false });\n\n // 2. get principal name from session\n const name = session.getAttribute(PRINCIPAL_NAME_INDEX_NAME);\n session.setLastAccessedTime(Date.now());\n await this.repository.save(session);\n\n // 3. call onLogged\n if (name !== null) await onLogged?.({ name: String(name) });\n\n // 4. renew cookie\n const response = Response.json({ data: name !== null });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n\n return response;\n };\n\n oauth2Authorization = async (request: Request): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const { registrationId } = param(request, this.PATH_OAUTH2_AUTHORIZATION);\n const state = randomUUID();\n const codeVerifier = randomUUID();\n const uri = this.oauth2Client.createAuthorizationUri(registrationId, state, codeVerifier);\n const sessionId = getCookie(request, this.cookieName);\n const session = sessionId\n ? ((await this.repository.findById(sessionId)) ?? this.repository.createSession())\n : this.repository.createSession();\n\n const authorizationRequest: OAuth2AuthorizationRequest = {\n state,\n codeVerifier,\n registrationId,\n authorizationRequestUri: uri.href,\n };\n\n const value = JSON.stringify(authorizationRequest);\n session.setAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST, value);\n await this.repository.save(session);\n\n const response = new Response(null, { status: 302, headers: { location: uri.href } });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n return response;\n };\n\n private redirect = (error: string): Response => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n return Response.redirect(`${this.oauth2Client.errorUri}?error=${error}`, 302);\n };\n\n loginOAuth2 = async (\n request: Request,\n onAuthorized: OAuth2AuthorizedHandler\n ): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n\n // 1. get session from cookie\n const sessionId = getCookie(request, this.cookieName);\n const session = sessionId ? await this.repository.findById(sessionId) : null;\n if (!session) {\n return this.redirect('OAUTH2_AUTHORIZATION_SESSION_NOT_FOUND');\n }\n\n // 2. get cached authorization request from session\n const json = session.getAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST) as string | null;\n if (!json) {\n return this.redirect('OAUTH2_AUTHORIZATION_REQUEST_NOT_FOUND');\n }\n const cached: OAuth2AuthorizationRequest = JSON.parse(json);\n\n // 3. validate redirect query/formdata\n const { registrationId } = param(request, this.PATH_OAUTH2_REDIRECT);\n let data: Record<string, string>;\n // apple redirect: response_mode=form_post\n if (\n request.method === 'POST' &&\n request.headers.get('content-type')?.toLowerCase() === 'application/x-www-form-urlencoded'\n ) {\n const params = new URLSearchParams(await request.text());\n data = Object.fromEntries(params.entries());\n } else {\n data = query(request);\n }\n\n const parsed = oauth2RedirectQuerySchema.safeParse(data);\n if (!parsed.success) {\n return this.redirect('INVALID_OAUTH2_REDIRECT_QUERY');\n }\n if (parsed.data.state !== cached.state) {\n return this.redirect('INVALID_OAUTH2_REDIRECT_STATE');\n }\n if (registrationId !== cached.registrationId) {\n return this.redirect('INVALID_OAUTH2_REDIRECT_REGISTRATION_ID');\n }\n\n // 4. exchange authorization code for token and get user info\n const { code } = parsed.data;\n const { codeVerifier } = cached;\n const token = await this.oauth2Client.exchangeAuthorizationCode(\n registrationId,\n code,\n codeVerifier\n );\n const userInfo = await this.oauth2Client.getUserInfo(registrationId, token);\n\n // 5. create or update principal\n const principal = await onAuthorized(request, registrationId, userInfo, token);\n\n // 6. update session\n session.setLastAccessedTime(Date.now());\n session.removeAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST);\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n\n const response = new Response(null, {\n status: 302,\n headers: { location: this.oauth2Client.baseUri },\n });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n return response;\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/auth/index.ts"],"sourcesContent":["import { createHash, randomUUID } from 'crypto';\nimport invariant from 'tiny-invariant';\nimport { PRINCIPAL_NAME_INDEX_NAME, type SessionRepository } from '../session/index';\nimport {\n param,\n query,\n getCookie,\n setCookie,\n deleteCookie,\n type CookieOptions,\n} from '../utils/http';\nimport { OAuth2Client, oauth2RedirectQuerySchema } from '../oauth2/client';\nimport { OAuth2ErrorType } from '../oauth2/error';\nimport type {\n NativeCredentials,\n OAuth2AuthorizationRequest,\n PkceParameters,\n} from '../oauth2/types';\nimport type {\n AuthConfig,\n AuthService,\n LoggedHandler,\n OAuth2AuthorizedHandler,\n OAuth2State,\n} from './types';\n\nexport class Auth implements AuthService {\n private readonly cookieName;\n private readonly cookieOptions: CookieOptions;\n private readonly repository: SessionRepository;\n private readonly oauth2Client: OAuth2Client | null;\n\n public readonly PATH_CSRF = '/csrf' as const;\n public readonly PATH_LOGOUT = '/logout' as const;\n public readonly PATH_LOGGED = '/logged' as const;\n\n public readonly PATH_OAUTH2_STATE = '/oauth2/state/:registrationId' as const;\n public readonly PATH_OAUTH2_AUTHORIZATION = '/oauth2/authorization/:registrationId' as const;\n public readonly PATH_LOGIN_OAUTH2_CODE = '/login/oauth2/code/:registrationId' as const;\n public readonly PATH_LOGIN_OAUTH2_NATIVE = '/login/oauth2/native/:registrationId' as const;\n\n public readonly ATTR_OAUTH2_AUTHORIZATION_REQUEST = 'oauth2AuthorizationRequest';\n\n constructor({ repository, oauth2, cookie }: AuthConfig) {\n this.repository = repository;\n const { name, ...cookieOptions } = cookie ?? {};\n this.cookieName = name ?? 'SESSION';\n this.cookieOptions = {\n path: '/',\n sameSite: 'none',\n secure: true,\n httpOnly: true,\n ...cookieOptions,\n };\n this.oauth2Client = oauth2?.client ? new OAuth2Client(oauth2.client) : null;\n }\n\n logout = async (request: Request): Promise<Response> => {\n const sessionId = getCookie(request, this.cookieName);\n if (sessionId) await this.repository.deleteById(sessionId);\n const response = Response.json({});\n deleteCookie(response, this.cookieName);\n return response;\n };\n\n logged = async (request: Request, onLogged?: LoggedHandler): Promise<Response> => {\n // 1. get session from cookie\n const sessionId = getCookie(request, this.cookieName);\n if (!sessionId) return Response.json({ data: false });\n const session = await this.repository.findById(sessionId);\n if (!session) return Response.json({ data: false });\n\n // 2. get principal name from session\n const name = session.getAttribute(PRINCIPAL_NAME_INDEX_NAME);\n session.setLastAccessedTime(Date.now());\n await this.repository.save(session);\n\n // 3. call onLogged\n if (name !== null) await onLogged?.({ name: String(name) });\n\n // 4. renew cookie\n const response = Response.json({ data: name !== null });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n\n return response;\n };\n\n private createPkceParameters(): PkceParameters {\n const code_verifier = randomUUID();\n const code_challenge = createHash('sha256')\n .update(code_verifier)\n .digest()\n .toString('base64url');\n\n return { code_verifier, code_challenge, code_challenge_method: 'S256' };\n }\n\n private getOauth2StateKey(state: string) {\n return `oauth2:state:${state}`;\n }\n\n oauth2State = async (request: Request): Promise<Response> => {\n const { registrationId } = param(request, this.PATH_OAUTH2_STATE);\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const state = randomUUID();\n const nonce = randomUUID();\n const pkce = this.createPkceParameters();\n const value: OAuth2State = { state, nonce, registrationId, ...pkce };\n await this.repository.setItem(this.getOauth2StateKey(state), value, 10 * 60);\n return Response.json({\n state,\n nonce,\n code_challenge: pkce.code_challenge,\n code_challenge_method: pkce.code_challenge_method,\n });\n };\n\n oauth2Authorization = async (request: Request): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const { registrationId } = param(request, this.PATH_OAUTH2_AUTHORIZATION);\n const state = randomUUID();\n const pkce = this.createPkceParameters();\n const uri = this.oauth2Client.createAuthorizationUri({ registrationId, state, pkce });\n const sessionId = getCookie(request, this.cookieName);\n const session = sessionId\n ? ((await this.repository.findById(sessionId)) ?? this.repository.createSession())\n : this.repository.createSession();\n\n const authorizationRequest: OAuth2AuthorizationRequest = {\n state,\n registrationId,\n authorizationRequestUri: uri.href,\n additionalParameters: pkce,\n };\n\n const value = JSON.stringify(authorizationRequest);\n session.setAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST, value);\n await this.repository.save(session);\n\n const response = new Response(null, { status: 302, headers: { location: uri.href } });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n return response;\n };\n\n private redirect = (error: OAuth2ErrorType, description?: string): Response => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const uri = new URL(this.oauth2Client.errorUri);\n uri.searchParams.set('error', error);\n if (description) uri.searchParams.set('error_description', description);\n return Response.redirect(uri.href, 302);\n };\n\n loginOAuth2Code = async (\n request: Request,\n onAuthorized: OAuth2AuthorizedHandler\n ): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n\n // 1. get session from cookie\n const sessionId = getCookie(request, this.cookieName);\n const session = sessionId ? await this.repository.findById(sessionId) : null;\n if (!session) {\n return this.redirect('invalid_request', 'session not found');\n }\n\n // 2. get cached authorization request from session\n const json = session.getAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST) as string | null;\n if (!json) {\n return this.redirect('invalid_request', 'authorization request not found');\n }\n const cached: OAuth2AuthorizationRequest = JSON.parse(json);\n\n // 3. validate redirect query/formdata\n const { registrationId } = param(request, this.PATH_LOGIN_OAUTH2_CODE);\n let data: Record<string, string>;\n // apple redirect: response_mode=form_post\n if (\n request.method === 'POST' &&\n request.headers.get('content-type')?.toLowerCase() === 'application/x-www-form-urlencoded'\n ) {\n const params = new URLSearchParams(await request.text());\n data = Object.fromEntries(params.entries());\n } else {\n data = query(request);\n }\n\n const parsed = oauth2RedirectQuerySchema.safeParse(data);\n if (!parsed.success) {\n return this.redirect('invalid_request', 'invalid redirect query');\n }\n if (!parsed.data.code || !parsed.data.state) {\n return this.redirect('invalid_request', 'invalid redirect query');\n }\n if (parsed.data.state !== cached.state) {\n return this.redirect('invalid_request', 'redirect state mismatch');\n }\n if (registrationId !== cached.registrationId) {\n return this.redirect('invalid_request', 'redirect registration mismatch');\n }\n\n // 4. exchange authorization code for token and get user info\n const { code } = parsed.data;\n\n const token = await this.oauth2Client.exchangeAuthorizationCode({\n registrationId,\n code,\n pkce: cached.additionalParameters,\n });\n const userInfo = await this.oauth2Client.getUserInfo({ registrationId, token });\n\n // 5. create or update principal\n const principal = await onAuthorized(request, registrationId, userInfo, token);\n\n // 6. update session\n session.setLastAccessedTime(Date.now());\n session.removeAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST);\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n\n const response = new Response(null, {\n status: 302,\n headers: { location: this.oauth2Client.successUri },\n });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n return response;\n };\n\n loginOAuth2Native = async (\n request: Request,\n onAuthorized: OAuth2AuthorizedHandler\n ): Promise<Response> => {\n invariant(this.oauth2Client, 'oauth2Client is not initialized');\n const { registrationId } = param(request, this.PATH_LOGIN_OAUTH2_NATIVE);\n const credentials = (await request.json()) as NativeCredentials;\n const key = this.getOauth2StateKey(credentials.state);\n const cached = await this.repository.getItem<OAuth2State>(key);\n if (!cached) {\n const json = { error: 'invalid_request', error_description: 'oauth2 state not found' };\n return Response.json(json, { status: 400 });\n }\n if (cached.registrationId !== registrationId) {\n const json = { error: 'invalid_request', error_description: 'registration mismatch' };\n return Response.json(json, { status: 400 });\n }\n const pkce = {\n code_verifier: cached.code_verifier,\n code_challenge: cached.code_challenge,\n code_challenge_method: cached.code_challenge_method,\n };\n\n const { userInfo, token } = await this.oauth2Client.loginOAuth2Native({\n registrationId,\n credentials,\n pkce,\n });\n const principal = await onAuthorized(request, registrationId, userInfo, token);\n const session = this.repository.createSession();\n session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);\n await this.repository.save(session);\n const response = new Response(JSON.stringify(principal), {\n status: 200,\n headers: { 'Content-Type': 'application/json' },\n });\n const maxAge = session.getMaxInactiveInterval();\n setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });\n return response;\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/auth/types.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/auth/types.ts"],"sourcesContent":["import { Principal } from '../core';\nimport { OAuth2ClientConfig, UserInfo } from '../oauth2/types';\nimport
|
|
1
|
+
{"version":3,"sources":["../../src/auth/types.ts"],"sourcesContent":["import type { Principal } from '../core';\nimport type { OAuth2Token, OAuth2ClientConfig, PkceParameters, UserInfo } from '../oauth2/types';\nimport type { SessionRepository } from '../session';\nimport type { CookieOptions } from '../utils/http';\n\nexport type LoggedHandler = (principal: Principal) => Promise<void>;\nexport type OAuth2AuthorizedHandler = (\n request: Request,\n registrationId: string,\n userInfo: UserInfo,\n token: OAuth2Token\n) => Promise<Principal>;\n\nexport interface OAuth2State extends PkceParameters {\n state: string;\n nonce: string;\n registrationId: string;\n}\n\nexport interface AuthConfig {\n repository: SessionRepository;\n cookie?: CookieOptions & { name?: string };\n oauth2?: {\n client?: OAuth2ClientConfig;\n };\n}\n\nexport interface AuthService {\n logout: (request: Request) => Promise<Response>;\n logged: (request: Request, onLogged?: LoggedHandler) => Promise<Response>;\n\n oauth2State: (request: Request) => Promise<Response>;\n oauth2Authorization: (request: Request) => Promise<Response>;\n loginOAuth2Code: (request: Request, onAuthorized: OAuth2AuthorizedHandler) => Promise<Response>;\n loginOAuth2Native: (request: Request, onAuthorized: OAuth2AuthorizedHandler) => Promise<Response>;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
|
package/dist/auth/types.d.cts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { Principal } from '../core/index.cjs';
|
|
2
|
-
import { UserInfo, OAuth2Token, OAuth2ClientConfig } from '../oauth2/types.cjs';
|
|
2
|
+
import { UserInfo, OAuth2Token, OAuth2ClientConfig, PkceParameters } from '../oauth2/types.cjs';
|
|
3
3
|
import { SessionRepository } from '../session/types.cjs';
|
|
4
4
|
import { CookieOptions } from '../utils/http.cjs';
|
|
5
5
|
import 'cookie';
|
|
6
6
|
|
|
7
7
|
type LoggedHandler = (principal: Principal) => Promise<void>;
|
|
8
8
|
type OAuth2AuthorizedHandler = (request: Request, registrationId: string, userInfo: UserInfo, token: OAuth2Token) => Promise<Principal>;
|
|
9
|
+
interface OAuth2State extends PkceParameters {
|
|
10
|
+
state: string;
|
|
11
|
+
nonce: string;
|
|
12
|
+
registrationId: string;
|
|
13
|
+
}
|
|
9
14
|
interface AuthConfig {
|
|
10
15
|
repository: SessionRepository;
|
|
11
16
|
cookie?: CookieOptions & {
|
|
@@ -18,8 +23,10 @@ interface AuthConfig {
|
|
|
18
23
|
interface AuthService {
|
|
19
24
|
logout: (request: Request) => Promise<Response>;
|
|
20
25
|
logged: (request: Request, onLogged?: LoggedHandler) => Promise<Response>;
|
|
26
|
+
oauth2State: (request: Request) => Promise<Response>;
|
|
21
27
|
oauth2Authorization: (request: Request) => Promise<Response>;
|
|
22
|
-
|
|
28
|
+
loginOAuth2Code: (request: Request, onAuthorized: OAuth2AuthorizedHandler) => Promise<Response>;
|
|
29
|
+
loginOAuth2Native: (request: Request, onAuthorized: OAuth2AuthorizedHandler) => Promise<Response>;
|
|
23
30
|
}
|
|
24
31
|
|
|
25
|
-
export type { AuthConfig, AuthService, LoggedHandler, OAuth2AuthorizedHandler };
|
|
32
|
+
export type { AuthConfig, AuthService, LoggedHandler, OAuth2AuthorizedHandler, OAuth2State };
|
package/dist/auth/types.d.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { Principal } from '../core/index.js';
|
|
2
|
-
import { UserInfo, OAuth2Token, OAuth2ClientConfig } from '../oauth2/types.js';
|
|
2
|
+
import { UserInfo, OAuth2Token, OAuth2ClientConfig, PkceParameters } from '../oauth2/types.js';
|
|
3
3
|
import { SessionRepository } from '../session/types.js';
|
|
4
4
|
import { CookieOptions } from '../utils/http.js';
|
|
5
5
|
import 'cookie';
|
|
6
6
|
|
|
7
7
|
type LoggedHandler = (principal: Principal) => Promise<void>;
|
|
8
8
|
type OAuth2AuthorizedHandler = (request: Request, registrationId: string, userInfo: UserInfo, token: OAuth2Token) => Promise<Principal>;
|
|
9
|
+
interface OAuth2State extends PkceParameters {
|
|
10
|
+
state: string;
|
|
11
|
+
nonce: string;
|
|
12
|
+
registrationId: string;
|
|
13
|
+
}
|
|
9
14
|
interface AuthConfig {
|
|
10
15
|
repository: SessionRepository;
|
|
11
16
|
cookie?: CookieOptions & {
|
|
@@ -18,8 +23,10 @@ interface AuthConfig {
|
|
|
18
23
|
interface AuthService {
|
|
19
24
|
logout: (request: Request) => Promise<Response>;
|
|
20
25
|
logged: (request: Request, onLogged?: LoggedHandler) => Promise<Response>;
|
|
26
|
+
oauth2State: (request: Request) => Promise<Response>;
|
|
21
27
|
oauth2Authorization: (request: Request) => Promise<Response>;
|
|
22
|
-
|
|
28
|
+
loginOAuth2Code: (request: Request, onAuthorized: OAuth2AuthorizedHandler) => Promise<Response>;
|
|
29
|
+
loginOAuth2Native: (request: Request, onAuthorized: OAuth2AuthorizedHandler) => Promise<Response>;
|
|
23
30
|
}
|
|
24
31
|
|
|
25
|
-
export type { AuthConfig, AuthService, LoggedHandler, OAuth2AuthorizedHandler };
|
|
32
|
+
export type { AuthConfig, AuthService, LoggedHandler, OAuth2AuthorizedHandler, OAuth2State };
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { Auth } from './auth/index';\nexport { OAuth2Client, oauth2RedirectQuerySchema } from './oauth2/client';\nexport { RedisIndexedSessionRepository, PRINCIPAL_NAME_INDEX_NAME } from './session/redis-session';\n\nexport type { Namespace, SessionRepository } from './session/types';\nexport type { StandardClaims, OAuth2Token, UserInfo } from './oauth2/types';\nexport type { Principal } from './core/index';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAAqB;AACrB,oBAAwD;AACxD,2BAAyE;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { Auth } from './auth/index';\nexport { OAuth2Client, oauth2RedirectQuerySchema } from './oauth2/client';\nexport { RedisIndexedSessionRepository, PRINCIPAL_NAME_INDEX_NAME } from './session/redis-session';\n\nexport type { Namespace, SessionRepository } from './session/types';\nexport type { StandardClaims, OAuth2Token, UserInfo, OidcToken, OidcScopes } from './oauth2/types';\nexport type { Principal } from './core/index';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,kBAAqB;AACrB,oBAAwD;AACxD,2BAAyE;","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -2,7 +2,7 @@ export { Auth } from './auth/index.cjs';
|
|
|
2
2
|
export { OAuth2Client, oauth2RedirectQuerySchema } from './oauth2/client.cjs';
|
|
3
3
|
export { PRINCIPAL_NAME_INDEX_NAME, RedisIndexedSessionRepository } from './session/redis-session.cjs';
|
|
4
4
|
export { Namespace, SessionRepository } from './session/types.cjs';
|
|
5
|
-
export { OAuth2Token, StandardClaims, UserInfo } from './oauth2/types.cjs';
|
|
5
|
+
export { OAuth2Token, OidcScopes, OidcToken, StandardClaims, UserInfo } from './oauth2/types.cjs';
|
|
6
6
|
export { Principal } from './core/index.cjs';
|
|
7
7
|
import './auth/types.cjs';
|
|
8
8
|
import './utils/http.cjs';
|
package/dist/index.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export { Auth } from './auth/index.js';
|
|
|
2
2
|
export { OAuth2Client, oauth2RedirectQuerySchema } from './oauth2/client.js';
|
|
3
3
|
export { PRINCIPAL_NAME_INDEX_NAME, RedisIndexedSessionRepository } from './session/redis-session.js';
|
|
4
4
|
export { Namespace, SessionRepository } from './session/types.js';
|
|
5
|
-
export { OAuth2Token, StandardClaims, UserInfo } from './oauth2/types.js';
|
|
5
|
+
export { OAuth2Token, OidcScopes, OidcToken, StandardClaims, UserInfo } from './oauth2/types.js';
|
|
6
6
|
export { Principal } from './core/index.js';
|
|
7
7
|
import './auth/types.js';
|
|
8
8
|
import './utils/http.js';
|