Merged in feature/curity-social-login (pull request #2963)

feat(SW-3541): Do social login after login to SAS

* feat(auth): wip social login via curity

* Setup social login auth flow

* Merge branch 'master' of bitbucket.org:scandic-swap/web into feature/curity-social-login

* Added support for getting scandic tokens and refresh them

* feat: Enhance social login and session management with auto-refresh and improved error handling

* Merge branch 'master' of bitbucket.org:scandic-swap/web into feature/curity-social-login

* wrap layout in suspense

* revert app/layout.tsx

* fix import

* cleanup

* merge

* merge

* dont pass client_secret in the url to curity

* add state validation when doing social login through /authorize

* remove debug logging


Approved-by: Anton Gunnarsson
This commit is contained in:
Joakim Jäderberg
2025-10-16 12:47:12 +00:00
parent 1850cfd20d
commit 291310e841
24 changed files with 827 additions and 84 deletions

View File

@@ -0,0 +1,13 @@
import "server-only"
import { env } from "@/env/server"
export const config = {
issuer: env.CURITY_ISSUER_USER,
client_id: "whitelabel-sas-social-login",
client_secret: env.CURITY_CLIENT_SECRET_USER,
redirect_uri: new URL("/api/web/auth/callback/curity", env.PUBLIC_URL).href,
acr_values: "urn:com:scandichotels:sas-eb",
scope: "openid profile availability availability_whitelabel_get",
response_type: "code",
} as const

View File

@@ -0,0 +1,13 @@
import { env } from "@/env/server"
import { config } from "@/auth/scandic/config"
export const endpoints = {
authorization_endpoint: new URL(
`/oauth/v2/authorize?allow=local&ui_locales=en&version=2&for_origin=${env.PUBLIC_URL}`,
config.issuer
),
token_endpoint: new URL("/oauth/v2/token?allow=local", config.issuer),
userinfo_endpoint: new URL("/oauth/v2/userinfo?allow=local", config.issuer),
end_session_endpoint: new URL("/oauth/v2/logout?allow=local", config.issuer),
} as const

View File

@@ -0,0 +1,38 @@
import { config } from "./config"
import { endpoints } from "./endpoints"
export async function getToken({ code }: { code: string }) {
const params = new URLSearchParams({
grant_type: "authorization_code",
code,
redirect_uri: config.redirect_uri,
client_id: config.client_id,
client_secret: config.client_secret,
})
const res = await fetch(endpoints.token_endpoint.toString(), {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
Accept: "application/json",
},
body: params,
signal: AbortSignal.timeout(15_000),
})
if (!res.ok) {
const text = await res.text()
throw new Error(`Token endpoint returned ${res.status}: ${text}`)
}
const payload = await res.json()
return payload as {
access_token: string
token_type?: string
expires_in: number
refresh_token?: string
id_token?: string
scope?: string
}
}

View File

@@ -0,0 +1,46 @@
import "server-only"
import { getIronSession } from "iron-session"
import { cookies } from "next/headers"
import { dt } from "@scandic-hotels/common/dt"
import { env } from "@/env/server"
export async function getSession() {
return getIronSession<{
access_token: string
refresh_token: string | undefined
expires_at: string
}>(await cookies(), {
password: env.IRON_SESSION_SECRET,
cookieName: "scandic_session",
})
}
export async function createSession({
access_token,
refresh_token,
expires_in,
}: {
access_token: string
expires_in: number
refresh_token?: string
}) {
const session = await getSession()
session.access_token = access_token
session.refresh_token = refresh_token
session.expires_at = dt()
.add(expires_in * 1000)
.toISOString()
await session.save()
}
export async function destroySession() {
const session = await getSession()
if (!session) return
session.destroy()
}