@shware/security 0.4.7 → 0.4.8

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.
@@ -100,7 +100,7 @@ var Auth = class {
100
100
  const value = JSON.stringify(authorizationRequest);
101
101
  session.setAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST, value);
102
102
  await this.repository.save(session);
103
- const response = Response.redirect(uri.href, 302);
103
+ const response = new Response(null, { status: 302, headers: { location: uri.href } });
104
104
  const maxAge = session.getMaxInactiveInterval();
105
105
  (0, import_http.setCookie)(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });
106
106
  return response;
@@ -152,7 +152,10 @@ var Auth = class {
152
152
  session.removeAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST);
153
153
  session.setAttribute(import_session.PRINCIPAL_NAME_INDEX_NAME, principal.name);
154
154
  await this.repository.save(session);
155
- const response = Response.redirect(this.oauth2Client.baseUri, 302);
155
+ const response = new Response(null, {
156
+ status: 302,
157
+ headers: { location: this.oauth2Client.baseUri }
158
+ });
156
159
  const maxAge = session.getMaxInactiveInterval();
157
160
  (0, import_http.setCookie)(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });
158
161
  return response;
@@ -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 = 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"]}
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"]}
@@ -72,7 +72,7 @@ var Auth = class {
72
72
  const value = JSON.stringify(authorizationRequest);
73
73
  session.setAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST, value);
74
74
  await this.repository.save(session);
75
- const response = Response.redirect(uri.href, 302);
75
+ const response = new Response(null, { status: 302, headers: { location: uri.href } });
76
76
  const maxAge = session.getMaxInactiveInterval();
77
77
  setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });
78
78
  return response;
@@ -124,7 +124,10 @@ var Auth = class {
124
124
  session.removeAttribute(this.ATTR_OAUTH2_AUTHORIZATION_REQUEST);
125
125
  session.setAttribute(PRINCIPAL_NAME_INDEX_NAME, principal.name);
126
126
  await this.repository.save(session);
127
- const response = Response.redirect(this.oauth2Client.baseUri, 302);
127
+ const response = new Response(null, {
128
+ status: 302,
129
+ headers: { location: this.oauth2Client.baseUri }
130
+ });
128
131
  const maxAge = session.getMaxInactiveInterval();
129
132
  setCookie(response, this.cookieName, session.getId(), { ...this.cookieOptions, maxAge });
130
133
  return response;
@@ -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 = 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":[]}
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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shware/security",
3
- "version": "0.4.7",
3
+ "version": "0.4.8",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "scripts": {