Merged in fix/SW-3578-user-is-forced-to-login- (pull request #3044)
fix(SW-3578): Fixed session issue when sas session expires * fix(SW-3578): Fixed session issue when sas session expires * base socialLogin auto-features on validSession and being linked * remove unused object * remove 'import server-only' for isValidSession() since it's only using passed data * remove isValidClientSession() Approved-by: Joakim Jäderberg Approved-by: Anton Gunnarsson
This commit is contained in:
committed by
Joakim Jäderberg
parent
15a2da333d
commit
1c7f72e95d
@@ -3,15 +3,11 @@
|
|||||||
import { useSession } from "next-auth/react"
|
import { useSession } from "next-auth/react"
|
||||||
|
|
||||||
import { BookingFlowContextProvider } from "@scandic-hotels/booking-flow/BookingFlowContextProvider"
|
import { BookingFlowContextProvider } from "@scandic-hotels/booking-flow/BookingFlowContextProvider"
|
||||||
import { dt } from "@scandic-hotels/common/dt"
|
|
||||||
import { createLogger } from "@scandic-hotels/common/logger/createLogger"
|
|
||||||
import { trpc } from "@scandic-hotels/trpc/client"
|
import { trpc } from "@scandic-hotels/trpc/client"
|
||||||
|
import { isValidSession } from "@scandic-hotels/trpc/utils/session"
|
||||||
|
|
||||||
import type { Session } from "next-auth"
|
|
||||||
import type { ComponentProps, ReactNode } from "react"
|
import type { ComponentProps, ReactNode } from "react"
|
||||||
|
|
||||||
const logger = createLogger("BookingFlowProviders")
|
|
||||||
|
|
||||||
export function BookingFlowProviders({ children }: { children: ReactNode }) {
|
export function BookingFlowProviders({ children }: { children: ReactNode }) {
|
||||||
const user = useBookingFlowUser()
|
const user = useBookingFlowUser()
|
||||||
const isLinkedUser =
|
const isLinkedUser =
|
||||||
@@ -36,7 +32,7 @@ type BookingFlowUser = BookingFlowContextData["user"]
|
|||||||
|
|
||||||
function useBookingFlowUser(): BookingFlowUser {
|
function useBookingFlowUser(): BookingFlowUser {
|
||||||
const { data: session } = useSession()
|
const { data: session } = useSession()
|
||||||
const hasValidSession = isValidClientSession(session)
|
const hasValidSession = isValidSession(session)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: euroBonusProfile,
|
data: euroBonusProfile,
|
||||||
@@ -66,25 +62,3 @@ function useBookingFlowUser(): BookingFlowUser {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isValidClientSession(session: Session | null) {
|
|
||||||
if (!session) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (session.error) {
|
|
||||||
logger.error(`Session error: ${session.error}`)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session.token.error) {
|
|
||||||
logger.error(`Session token error: ${session.token.error}`)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
const expiresAt = dt(session.token.expires_at)
|
|
||||||
if (session.token.expires_at && expiresAt.isBefore(dt())) {
|
|
||||||
logger.warn(`Session expired: ${expiresAt.toISOString()}`)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { useEffect } from "react"
|
|||||||
|
|
||||||
import { dt } from "@scandic-hotels/common/dt"
|
import { dt } from "@scandic-hotels/common/dt"
|
||||||
import { createLogger } from "@scandic-hotels/common/logger/createLogger"
|
import { createLogger } from "@scandic-hotels/common/logger/createLogger"
|
||||||
|
import { isValidSession } from "@scandic-hotels/trpc/utils/session"
|
||||||
|
|
||||||
import type { User } from "next-auth"
|
import type { User } from "next-auth"
|
||||||
|
|
||||||
@@ -26,10 +27,12 @@ export function useSocialSession() {
|
|||||||
|
|
||||||
function useSocialSessionQuery() {
|
function useSocialSessionQuery() {
|
||||||
const { data: session } = useSession()
|
const { data: session } = useSession()
|
||||||
|
const enabled = isValidSession(session) && isUserLinked(session?.user)
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: ["socialSession"],
|
queryKey: ["socialSession"],
|
||||||
queryFn: getSocialSession,
|
queryFn: getSocialSession,
|
||||||
enabled: !!session,
|
enabled: enabled,
|
||||||
refetchInterval: getTime(1, "m"),
|
refetchInterval: getTime(1, "m"),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -38,7 +41,8 @@ function useAutoLogin() {
|
|||||||
const { data: session } = useSession()
|
const { data: session } = useSession()
|
||||||
const { isSuccess, data: socialSession } = useSocialSessionQuery()
|
const { isSuccess, data: socialSession } = useSocialSessionQuery()
|
||||||
|
|
||||||
const isLinked = isLinkedUser(session?.user) ? session.user.isLinked : false
|
const isLinked = isUserLinked(session?.user)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isLinked) {
|
if (!isLinked) {
|
||||||
autoLoginLogger.info("User is not linked")
|
autoLoginLogger.info("User is not linked")
|
||||||
@@ -174,11 +178,10 @@ function getTime(value: number, unit: "m" | "s") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isLinkedUser(
|
function isUserLinked(user: User | undefined): boolean {
|
||||||
user: User | undefined
|
|
||||||
): user is User & { isLinked: boolean } {
|
|
||||||
if (user && "isLinked" in user) {
|
if (user && "isLinked" in user) {
|
||||||
return true
|
return !!user.isLinked
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import { Avatar } from "@scandic-hotels/design-system/Avatar"
|
|||||||
import { LoginButton } from "@scandic-hotels/design-system/LoginButton"
|
import { LoginButton } from "@scandic-hotels/design-system/LoginButton"
|
||||||
import { Typography } from "@scandic-hotels/design-system/Typography"
|
import { Typography } from "@scandic-hotels/design-system/Typography"
|
||||||
import { trpc } from "@scandic-hotels/trpc/client"
|
import { trpc } from "@scandic-hotels/trpc/client"
|
||||||
|
import { isValidSession } from "@scandic-hotels/trpc/utils/session"
|
||||||
|
|
||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
import { isValidClientSession } from "@/utils/clientSession"
|
|
||||||
import { trackLoginClick } from "@/utils/tracking"
|
import { trackLoginClick } from "@/utils/tracking"
|
||||||
|
|
||||||
import MyPagesMenu, { MyPagesMenuSkeleton } from "../MyPagesMenu"
|
import MyPagesMenu, { MyPagesMenuSkeleton } from "../MyPagesMenu"
|
||||||
@@ -27,7 +27,7 @@ export default function MyPagesMenuWrapper() {
|
|||||||
const loginPathname = useLazyPathname({ includeSearchParams: true })
|
const loginPathname = useLazyPathname({ includeSearchParams: true })
|
||||||
|
|
||||||
const { data: session } = useSession()
|
const { data: session } = useSession()
|
||||||
const isUserLoggedIn = isValidClientSession(session)
|
const isUserLoggedIn = isValidSession(session)
|
||||||
|
|
||||||
const { data: user, isLoading: isLoadingUser } = trpc.user.name.useQuery()
|
const { data: user, isLoading: isLoadingUser } = trpc.user.name.useQuery()
|
||||||
const { data: membership, isLoading: isLoadingMembership } =
|
const { data: membership, isLoading: isLoadingMembership } =
|
||||||
|
|||||||
@@ -5,15 +5,15 @@ import { useSession } from "next-auth/react"
|
|||||||
|
|
||||||
import { logoutSafely } from "@scandic-hotels/common/constants/routes/handleAuth"
|
import { logoutSafely } from "@scandic-hotels/common/constants/routes/handleAuth"
|
||||||
import { trpc } from "@scandic-hotels/trpc/client"
|
import { trpc } from "@scandic-hotels/trpc/client"
|
||||||
|
import { isValidSession } from "@scandic-hotels/trpc/utils/session"
|
||||||
|
|
||||||
import { userNotFound } from "@/constants/routes/errorPages"
|
import { userNotFound } from "@/constants/routes/errorPages"
|
||||||
|
|
||||||
import useLang from "@/hooks/useLang"
|
import useLang from "@/hooks/useLang"
|
||||||
import { isValidClientSession } from "@/utils/clientSession"
|
|
||||||
|
|
||||||
export function UserExists() {
|
export function UserExists() {
|
||||||
const { data: session } = useSession()
|
const { data: session } = useSession()
|
||||||
const isUserLoggedIn = isValidClientSession(session)
|
const isUserLoggedIn = isValidSession(session)
|
||||||
const lang = useLang()
|
const lang = useLang()
|
||||||
|
|
||||||
const { isLoading: isLoadingUser, error } = trpc.user.get.useQuery(
|
const { isLoading: isLoadingUser, error } = trpc.user.get.useQuery(
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { useSession } from "next-auth/react"
|
import { useSession } from "next-auth/react"
|
||||||
|
|
||||||
import { isValidClientSession } from "@/utils/clientSession"
|
import { isValidSession } from "@scandic-hotels/trpc/utils/session"
|
||||||
|
|
||||||
export function useIsUserLoggedIn() {
|
export function useIsUserLoggedIn() {
|
||||||
const { data: session } = useSession()
|
const { data: session } = useSession()
|
||||||
const isUserLoggedIn = isValidClientSession(session)
|
const isUserLoggedIn = isValidSession(session)
|
||||||
return isUserLoggedIn
|
return isUserLoggedIn
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
import { createLogger } from "@scandic-hotels/common/logger/createLogger"
|
|
||||||
|
|
||||||
import type { Session } from "next-auth"
|
|
||||||
|
|
||||||
const logger = createLogger("clientSession")
|
|
||||||
export function isValidClientSession(session: Session | null) {
|
|
||||||
if (!session) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (session.error) {
|
|
||||||
logger.error(`Session error: ${session.error}`)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (session.token.error) {
|
|
||||||
logger.error(`Session token error: ${session.token.error}`)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (session.token.expires_at && session.token.expires_at < Date.now()) {
|
|
||||||
logger.error(`Session expired: ${session.token.expires_at}`)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
58
packages/trpc/lib/utils/session.test.ts
Normal file
58
packages/trpc/lib/utils/session.test.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { beforeEach, describe, expect, it, vi } from "vitest"
|
||||||
|
|
||||||
|
import { isValidSession } from "./session"
|
||||||
|
|
||||||
|
vi.mock("@scandic-hotels/common/logger/createLogger", () => {
|
||||||
|
return {
|
||||||
|
createLogger: (_name: string) => ({
|
||||||
|
error: vi.fn(),
|
||||||
|
debug: vi.fn(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("isValidSession", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.restoreAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns false for null session and does not log", () => {
|
||||||
|
expect(isValidSession(null)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns false and logs when session.error is present", () => {
|
||||||
|
const s: any = { error: "bad" }
|
||||||
|
expect(isValidSession(s)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns false and logs when session.token.error is present", () => {
|
||||||
|
const s: any = { token: { error: "tokfail" } }
|
||||||
|
expect(isValidSession(s)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns false and logs when token.expires_at is in the past", () => {
|
||||||
|
const now = 1_700_000_000_000
|
||||||
|
vi.spyOn(Date, "now").mockReturnValue(now)
|
||||||
|
const s: any = { token: { expires_at: now - 1000 } }
|
||||||
|
expect(isValidSession(s)).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns true when token.expires_at equals Date.now()", () => {
|
||||||
|
const now = 1_700_000_000_000
|
||||||
|
vi.spyOn(Date, "now").mockReturnValue(now)
|
||||||
|
const s: any = { token: { expires_at: now } }
|
||||||
|
expect(isValidSession(s)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns true for session without token.expires_at", () => {
|
||||||
|
const s: any = { token: { access: "ok" } }
|
||||||
|
expect(isValidSession(s)).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns true for a fully valid session with future expires_at", () => {
|
||||||
|
const now = 1_700_000_000_000
|
||||||
|
vi.spyOn(Date, "now").mockReturnValue(now)
|
||||||
|
const s: any = { token: { expires_at: now + 10_000 } }
|
||||||
|
expect(isValidSession(s)).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
import "server-only"
|
|
||||||
|
|
||||||
import { createLogger } from "@scandic-hotels/common/logger/createLogger"
|
import { createLogger } from "@scandic-hotels/common/logger/createLogger"
|
||||||
|
|
||||||
import type { Session } from "next-auth"
|
import type { Session } from "next-auth"
|
||||||
|
|
||||||
|
const sessionLogger = createLogger("session")
|
||||||
export function isValidSession(session: Session | null): session is Session {
|
export function isValidSession(session: Session | null): session is Session {
|
||||||
const sessionLogger = createLogger("session")
|
|
||||||
if (!session) {
|
if (!session) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -15,13 +13,12 @@ export function isValidSession(session: Session | null): session is Session {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = session.token
|
if (session.token?.error) {
|
||||||
|
sessionLogger.error(`Session token error: ${session.token.error}`)
|
||||||
if (token?.error) {
|
|
||||||
sessionLogger.error(`Session token error: ${token.error}`)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (token?.expires_at && token.expires_at < Date.now()) {
|
|
||||||
|
if (session.token?.expires_at && session.token.expires_at < Date.now()) {
|
||||||
sessionLogger.debug(`Session expired: ${session.token.expires_at}`)
|
sessionLogger.debug(`Session expired: ${session.token.expires_at}`)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user