@shware/security 0.4.3 → 0.4.4

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.
@@ -122,7 +122,14 @@ var Auth = class {
122
122
  }
123
123
  const cached = JSON.parse(json);
124
124
  const { registrationId } = (0, import_http.param)(request, this.PATH_OAUTH2_REDIRECT);
125
- const parsed = import_client.oauth2RedirectQuerySchema.safeParse((0, import_http.query)(request));
125
+ let data;
126
+ if (request.method === "POST" && request.headers.get("content-type")?.toLowerCase() === "application/x-www-form-urlencoded") {
127
+ const params = new URLSearchParams(await request.text());
128
+ data = Object.fromEntries(params.entries());
129
+ } else {
130
+ data = (0, import_http.query)(request);
131
+ }
132
+ const parsed = import_client.oauth2RedirectQuerySchema.safeParse(data);
126
133
  if (!parsed.success) {
127
134
  return this.redirect("INVALID_OAUTH2_REDIRECT_QUERY");
128
135
  }
@@ -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';\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 = Response.redirect(uri.href, 302);\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\n const { registrationId } = param(request, this.PATH_OAUTH2_REDIRECT);\n const parsed = oauth2RedirectQuerySchema.safeParse(query(request));\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 = Response.redirect(this.oauth2Client.baseUri, 302);\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 { randomUUID } from 'crypto';\nimport invariant from 'tiny-invariant';\nimport { PRINCIPAL_NAME_INDEX_NAME, type SessionRepository } from '../session';\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 = Response.redirect(uri.href, 302);\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 = Response.redirect(this.oauth2Client.baseUri, 302);\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"]}
@@ -94,7 +94,14 @@ var Auth = class {
94
94
  }
95
95
  const cached = JSON.parse(json);
96
96
  const { registrationId } = param(request, this.PATH_OAUTH2_REDIRECT);
97
- const parsed = oauth2RedirectQuerySchema.safeParse(query(request));
97
+ let data;
98
+ if (request.method === "POST" && request.headers.get("content-type")?.toLowerCase() === "application/x-www-form-urlencoded") {
99
+ const params = new URLSearchParams(await request.text());
100
+ data = Object.fromEntries(params.entries());
101
+ } else {
102
+ data = query(request);
103
+ }
104
+ const parsed = oauth2RedirectQuerySchema.safeParse(data);
98
105
  if (!parsed.success) {
99
106
  return this.redirect("INVALID_OAUTH2_REDIRECT_QUERY");
100
107
  }
@@ -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';\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 = Response.redirect(uri.href, 302);\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\n const { registrationId } = param(request, this.PATH_OAUTH2_REDIRECT);\n const parsed = oauth2RedirectQuerySchema.safeParse(query(request));\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 = Response.redirect(this.oauth2Client.baseUri, 302);\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 { randomUUID } from 'crypto';\nimport invariant from 'tiny-invariant';\nimport { PRINCIPAL_NAME_INDEX_NAME, type SessionRepository } from '../session';\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 = Response.redirect(uri.href, 302);\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 = Response.redirect(this.oauth2Client.baseUri, 302);\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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shware/security",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "scripts": {