diff --git a/src/authentication-flows/oauth2.tsx b/src/authentication-flows/oauth2.tsx index fdc62644e..55ade7390 100644 --- a/src/authentication-flows/oauth2.tsx +++ b/src/authentication-flows/oauth2.tsx @@ -21,13 +21,13 @@ import { } from "../constants"; import { nanoid } from "nanoid"; import { Apple } from "../strategies/Apple"; +import { getClientInfo } from "../utils/client-info"; export async function socialAuth( ctx: Context<{ Bindings: Env; Variables: Var }>, client: Client, connectionName: string, authParams: AuthParams, - auth0Client?: string, ) { if (!authParams.state) { throw new HTTPException(400, { message: "State not found" }); @@ -46,24 +46,24 @@ export async function socialAuth( throw new HTTPException(403, { message: "Connection Not Found" }); } - let session = await ctx.env.data.logins.get( + let loginSession = await ctx.env.data.logins.get( client.tenant.id, authParams.state, ); - if (!session) { - session = await ctx.env.data.logins.create(client.tenant.id, { + if (!loginSession) { + loginSession = await ctx.env.data.logins.create(client.tenant.id, { expires_at: new Date( Date.now() + UNIVERSAL_AUTH_SESSION_EXPIRES_IN_SECONDS * 1000, ).toISOString(), authParams, - auth0Client, + ...getClientInfo(ctx.req), }); } const code_verifier = generateCodeVerifier(); const auth2State = await ctx.env.data.codes.create(client.tenant.id, { - login_id: session.login_id, + login_id: loginSession.login_id, code_id: nanoid(), code_type: "oauth2_state", code_verifier, diff --git a/src/authentication-flows/password.ts b/src/authentication-flows/password.ts index 376174b70..ce1131a8b 100644 --- a/src/authentication-flows/password.ts +++ b/src/authentication-flows/password.ts @@ -15,6 +15,7 @@ import { HTTPException } from "hono/http-exception"; import { CustomException } from "../models/CustomError"; import userIdGenerate from "../utils/userIdGenerate"; import { AuthParams, Client, LogTypes } from "authhero"; +import { getClientInfo } from "../utils/client-info"; export async function requestPasswordReset( ctx: Context<{ @@ -63,8 +64,7 @@ export async function requestPasswordReset( client_id: client.id, username: email, }, - useragent: ctx.req.header("user-agent"), - ip: ctx.req.header("x-real-ip"), + ...getClientInfo(ctx.req), }); let code_id = generateOTP(); diff --git a/src/authentication-flows/passwordless.ts b/src/authentication-flows/passwordless.ts index d23dfdd74..e788e470e 100644 --- a/src/authentication-flows/passwordless.ts +++ b/src/authentication-flows/passwordless.ts @@ -23,6 +23,7 @@ import { createLogMessage } from "../utils/create-log-message"; import { AuthParams, Client, LogTypes, Login, User } from "authhero"; import { preUserSignupHook } from "../hooks"; import { SendType } from "../utils/getSendParamFromAuth0ClientHeader"; +import { getClientInfo } from "../utils/client-info"; interface LoginParams { client_id: string; @@ -121,28 +122,22 @@ export async function sendEmailVerificationEmail({ username: user.email, }; - const login: Login = { - login_id: nanoid(), - created_at: new Date().toISOString(), - updated_at: new Date().toISOString(), + const loginSession = await env.data.logins.create(client.tenant.id, { expires_at: new Date( Date.now() + UNIVERSAL_AUTH_SESSION_EXPIRES_IN_SECONDS * 1000, ).toISOString(), authParams, - useragent: ctx.req.header("user-agent"), - ip: ctx.req.header("x-real-ip"), - }; - - await env.data.logins.create(client.tenant.id, login); + ...getClientInfo(ctx.req), + }); - const state = login.login_id; + const state = loginSession.login_id; const code_id = generateOTP(); await env.data.codes.create(client.tenant.id, { code_id, code_type: "email_verification", - login_id: login.login_id, + login_id: loginSession.login_id, expires_at: new Date(Date.now() + CODE_EXPIRATION_TIME).toISOString(), }); diff --git a/src/authentication-flows/universal.ts b/src/authentication-flows/universal.ts index 99653b0a7..4348bc5f3 100644 --- a/src/authentication-flows/universal.ts +++ b/src/authentication-flows/universal.ts @@ -5,6 +5,7 @@ import { AuthParams, Client, Session } from "authhero"; import { generateAuthResponse } from "../helpers/generate-auth-response"; import { sendOtpEmail } from "./passwordless"; import { getSendParamFromAuth0ClientHeader } from "../utils/getSendParamFromAuth0ClientHeader"; +import { getClientInfo } from "../utils/client-info"; interface UniversalAuthParams { ctx: Context<{ Bindings: Env; Variables: Var }>; @@ -25,14 +26,12 @@ export async function universalAuth({ connection, login_hint, }: UniversalAuthParams) { - const login = await ctx.env.data.logins.create(client.tenant.id, { + const loginSession = await ctx.env.data.logins.create(client.tenant.id, { expires_at: new Date( Date.now() + UNIVERSAL_AUTH_SESSION_EXPIRES_IN_SECONDS * 1000, ).toISOString(), authParams, - auth0Client, - useragent: ctx.req.header("user-agent"), - ip: ctx.req.header("x-real-ip"), + ...getClientInfo(ctx.req), }); // Check if the user in the login_hint matches the user in the session @@ -47,7 +46,7 @@ export async function universalAuth({ ctx, client, // This needs to match the login_id as it has the reference to the auth params - sid: login.login_id, + sid: loginSession.login_id, authParams, user, }); @@ -57,15 +56,15 @@ export async function universalAuth({ // If there's an email connectiona and a login_hint we redirect to the check-account page if (connection === "email" && login_hint) { const sendType = getSendParamFromAuth0ClientHeader(auth0Client); - await sendOtpEmail({ ctx, client, session: login, sendType }); + await sendOtpEmail({ ctx, client, session: loginSession, sendType }); - return ctx.redirect(`/u/enter-code?state=${login.login_id}`); + return ctx.redirect(`/u/enter-code?state=${loginSession.login_id}`); } // If there is a session we redirect to the check-account page if (session) { - return ctx.redirect(`/u/check-account?state=${login.login_id}`); + return ctx.redirect(`/u/check-account?state=${loginSession.login_id}`); } - return ctx.redirect(`/u/enter-email?state=${login.login_id}`); + return ctx.redirect(`/u/enter-email?state=${loginSession.login_id}`); } diff --git a/src/helpers/generate-auth-response.ts b/src/helpers/generate-auth-response.ts index a129fad27..32bb3c7e4 100644 --- a/src/helpers/generate-auth-response.ts +++ b/src/helpers/generate-auth-response.ts @@ -27,6 +27,7 @@ import { setSilentAuthCookies } from "./silent-auth-cookie"; import { samlResponseForm } from "../templates/samlResponse"; import { HTTPException } from "hono/http-exception"; import { createSamlResponse } from "./saml"; +import { getClientInfo } from "../utils/client-info"; export type AuthFlowType = | "cross-origin" @@ -84,8 +85,7 @@ async function generateCode({ Date.now() + UNIVERSAL_AUTH_SESSION_EXPIRES_IN_SECONDS * 1000, ).toISOString(), authParams, - useragent: ctx.req.header("user-agent"), - ip: ctx.req.header("x-real-ip"), + ...getClientInfo(ctx.req), }); login_id = login.login_id; diff --git a/src/routes/oauth2/authenticate.ts b/src/routes/oauth2/authenticate.ts index 663d51f59..0915af4c3 100644 --- a/src/routes/oauth2/authenticate.ts +++ b/src/routes/oauth2/authenticate.ts @@ -6,6 +6,7 @@ import { HTTPException } from "hono/http-exception"; import { getClient } from "../../services/clients"; import { loginWithPassword } from "../../authentication-flows/password"; import { Login } from "authhero"; +import { getClientInfo } from "../../utils/client-info"; function createUnauthorizedResponse() { return new HTTPException(403, { @@ -109,8 +110,7 @@ export const authenticateRoutes = new OpenAPIHono<{ client_id: client.id, username: email, }, - useragent: ctx.req.header("user-agent"), - ip: ctx.req.header("x-real-ip"), + ...getClientInfo(ctx.req), }); } else { throw new HTTPException(400, { message: "Code or password required" }); diff --git a/src/routes/oauth2/authorize.ts b/src/routes/oauth2/authorize.ts index b4c834493..2a082cc0c 100644 --- a/src/routes/oauth2/authorize.ts +++ b/src/routes/oauth2/authorize.ts @@ -166,18 +166,12 @@ export const authorizeRoutes = new OpenAPIHono<{ client.connections.length === 1 && !UI_STRATEGIES.includes(client.connections[0].strategy || "") ) { - return socialAuth( - ctx, - client, - client.connections[0].name, - authParams, - auth0Client, - ); + return socialAuth(ctx, client, client.connections[0].name, authParams); } // Social login if (connection && connection !== "email") { - return socialAuth(ctx, client, connection, authParams, auth0Client); + return socialAuth(ctx, client, connection, authParams); } else if (login_ticket) { return ticketAuth( ctx, @@ -191,8 +185,8 @@ export const authorizeRoutes = new OpenAPIHono<{ return universalAuth({ ctx, client, - authParams, auth0Client, + authParams, session: session || undefined, connection, login_hint, diff --git a/src/routes/oauth2/dbconnections.ts b/src/routes/oauth2/dbconnections.ts index 20f18c659..33994b36d 100644 --- a/src/routes/oauth2/dbconnections.ts +++ b/src/routes/oauth2/dbconnections.ts @@ -11,6 +11,7 @@ import { createLogMessage } from "../../utils/create-log-message"; import { requestPasswordReset } from "../../authentication-flows/password"; import { UNIVERSAL_AUTH_SESSION_EXPIRES_IN_SECONDS } from "../../constants"; import { AuthParams, LogTypes } from "authhero"; +import { getClientInfo } from "../../utils/client-info"; export const dbConnectionRoutes = new OpenAPIHono<{ Bindings: Env; @@ -160,16 +161,15 @@ export const dbConnectionRoutes = new OpenAPIHono<{ username: email, }; - const session = await ctx.env.data.logins.create(client.tenant.id, { + const loginSession = await ctx.env.data.logins.create(client.tenant.id, { expires_at: new Date( Date.now() + UNIVERSAL_AUTH_SESSION_EXPIRES_IN_SECONDS * 1000, ).toISOString(), authParams, - useragent: ctx.req.header("user-agent"), - ip: ctx.req.header("x-real-ip"), + ...getClientInfo(ctx.req), }); - await requestPasswordReset(ctx, client, email, session.login_id); + await requestPasswordReset(ctx, client, email, loginSession.login_id); return ctx.html("We've just sent you an email to reset your password."); }, diff --git a/src/routes/oauth2/passwordless.ts b/src/routes/oauth2/passwordless.ts index 6f80fc3a0..f1b2aa5ee 100644 --- a/src/routes/oauth2/passwordless.ts +++ b/src/routes/oauth2/passwordless.ts @@ -1,4 +1,3 @@ -import { nanoid } from "nanoid"; import generateOTP from "../../utils/otp"; import { getClient } from "../../services/clients"; import { sendCode, sendLink } from "../../controllers/email"; @@ -8,12 +7,12 @@ import { HTTPException } from "hono/http-exception"; import { validateCode } from "../../authentication-flows/passwordless"; import { validateRedirectUrl } from "../../utils/validate-redirect-url"; import { generateAuthResponse } from "../../helpers/generate-auth-response"; -import { setSearchParams } from "../../utils/url"; import { AuthParams, AuthorizationResponseType, authParamsSchema, } from "authhero"; +import { getClientInfo } from "../../utils/client-info"; const OTP_EXPIRATION_TIME = 30 * 60 * 1000; @@ -57,17 +56,16 @@ export const passwordlessRoutes = new OpenAPIHono<{ const { client_id, email, send, authParams } = body; const client = await getClient(env, client_id); - const login = await env.data.logins.create(client.tenant.id, { + const loginSession = await env.data.logins.create(client.tenant.id, { authParams: { ...authParams, client_id, username: email }, expires_at: new Date(Date.now() + OTP_EXPIRATION_TIME).toISOString(), - useragent: ctx.req.header("user-agent"), - ip: ctx.req.header("x-real-ip"), + ...getClientInfo(ctx.req), }); const code = await env.data.codes.create(client.tenant.id, { code_id: generateOTP(), code_type: "otp", - login_id: login.login_id, + login_id: loginSession.login_id, expires_at: new Date(Date.now() + OTP_EXPIRATION_TIME).toISOString(), }); diff --git a/src/routes/saml/samlp.ts b/src/routes/saml/samlp.ts index 80daf6733..3762366a7 100644 --- a/src/routes/saml/samlp.ts +++ b/src/routes/saml/samlp.ts @@ -6,6 +6,7 @@ import { getClient } from "../../services/clients"; import { UNIVERSAL_AUTH_SESSION_EXPIRES_IN_SECONDS } from "../../constants"; import { X509Certificate } from "@peculiar/x509"; import { createSamlMetadata, parseSamlRequestQuery } from "../../helpers/saml"; +import { getClientInfo } from "../../utils/client-info"; export const samlpRoutes = new OpenAPIHono<{ Bindings: Env; @@ -118,7 +119,7 @@ export const samlpRoutes = new OpenAPIHono<{ const issuer = samlRequest["samlp:AuthnRequest"]["saml:Issuer"]["#text"]; // Create a new Login session - const login = await ctx.env.data.logins.create(client.tenant.id, { + const loginSession = await ctx.env.data.logins.create(client.tenant.id, { authParams: { client_id: client.id, // TODO: A terrible hack to get the vendor_id @@ -137,10 +138,9 @@ export const samlpRoutes = new OpenAPIHono<{ expires_at: new Date( Date.now() + UNIVERSAL_AUTH_SESSION_EXPIRES_IN_SECONDS * 1000, ).toISOString(), - useragent: ctx.req.header("user-agent"), - ip: ctx.req.header("x-real-ip"), + ...getClientInfo(ctx.req), }); - return ctx.redirect(`/u/enter-email?state=${login.login_id}`); + return ctx.redirect(`/u/enter-email?state=${loginSession.login_id}`); }, ); diff --git a/src/utils/client-info.ts b/src/utils/client-info.ts new file mode 100644 index 000000000..a32c3c3fe --- /dev/null +++ b/src/utils/client-info.ts @@ -0,0 +1,9 @@ +import { HonoRequest } from "hono"; + +export function getClientInfo(req: HonoRequest) { + return { + auth0Client: req.query("auth0Client")?.slice(0, 256), + ip: req.header("x-real-ip")?.slice(0, 45), + useragent: req.header("user-agent")?.slice(0, 256), + }; +}