Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/core/src/api/credentials.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export const signInCredentials = async ({
redirectURL: null,
error: { code, message },
toResponse: () => {
return Response.json({ success: false, redirectURL: null, error: { code, message } }, { headers, status: 401 })
return Response.json({ success: false, redirectURL: null }, { headers, status: 401 })
},
}
if (error instanceof AuthValidationError) {
Expand Down
3 changes: 1 addition & 2 deletions packages/core/src/client/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import type {
SignOutReturn,
UpdateSessionReturn,
SignInCredentialsReturn,
SignInAPIReturn,
SignInCredentialsOptions,
} from "@/@types/index.ts"

Expand Down Expand Up @@ -73,7 +72,7 @@ export const createAuthClient = <DefaultUser extends User = User>(options: AuthC
redirect: false,
},
})
const json = (await response.json()) as SignInAPIReturn
const json = await response.json()
if ((options?.redirect ?? true) && typeof window !== "undefined" && json?.signInURL) {
window.location.assign(json.signInURL)
}
Expand Down
231 changes: 228 additions & 3 deletions packages/core/test/client/client.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ const createJSONResponse = (body: unknown, status = 200) => {
})
}

beforeEach(() => {
vi.clearAllMocks()
vi.unstubAllGlobals()
})

describe("createAuthClient", () => {
beforeEach(() => {
vi.clearAllMocks()
Expand Down Expand Up @@ -54,7 +59,22 @@ describe("createAuthClient", () => {
})
})

test("getSession returns valid session", async () => {
test("infer baseURL from window.location.origin in browser environment", () => {
vi.stubGlobal("window", { location: { origin: "https://example.com" } })
createClientMock.mockReturnValue({
get: vi.fn(),
post: vi.fn(),
})

createAuthClient({})
expect(createClientMock).toHaveBeenCalledWith({
baseURL: "https://example.com",
cache: "no-store",
credentials: "include",
})
})

test("getSession with valid session", async () => {
const get = vi.fn().mockResolvedValue(
createJSONResponse({
success: true,
Expand All @@ -74,7 +94,27 @@ describe("createAuthClient", () => {
expect(session).toEqual({ user: { id: "user_1" } })
})

test("getSession returns null for non-ok response", async () => {
test("getSession with no session", async () => {
const get = vi.fn().mockResolvedValue(
createJSONResponse({
success: false,
session: null,
})
)

createClientMock.mockReturnValue({
get,
post: vi.fn(),
})

const client = createAuthClient({ baseURL: "https://example.com" })
const session = await client.getSession()

expect(get).toHaveBeenCalledWith("/session")
expect(session).toBeNull()
})

test("getSession with 401 status code", async () => {
const get = vi.fn().mockResolvedValue(createJSONResponse({}, 401))

createClientMock.mockReturnValue({
Expand Down Expand Up @@ -107,25 +147,210 @@ describe("createAuthClient", () => {
})

test("signIn with redirect option", async () => {
vi.stubGlobal("window", { location: { assign: vi.fn() } })
const get = vi.fn().mockResolvedValue(createJSONResponse({ signInURL: "https://example.com/oauth" }))

createClientMock.mockReturnValue({
get,
post: vi.fn(),
})
const client = createAuthClient({ baseURL: "https://example.com" })
const response = await client.signIn("github", { redirect: false, redirectTo: "/dashboard" })
const response = await client.signIn("github", { redirect: true, redirectTo: "/dashboard" })

expect(get).toHaveBeenCalledWith("/signIn/:oauth", {
params: { oauth: "github" },
searchParams: {
// The redirect is set to false in the request to prevent automatic
// redirection by server response by 302 status code.
redirect: false,
redirectTo: "/dashboard",
},
})
expect(window.location.assign).toHaveBeenCalledWith("https://example.com/oauth")
expect(response).toEqual({ signInURL: "https://example.com/oauth" })
})

test("signInCredentials", async () => {
const post = vi.fn().mockResolvedValue(
createJSONResponse({
success: true,
redirectURL: "/",
})
)

createClientMock.mockReturnValue({
get: vi.fn(),
post,
})

const client = createAuthClient({ baseURL: "https://example.com" })
const response = await client.signInCredentials({ payload: { username: "user", password: "pass" } })

expect(post).toHaveBeenCalledWith("/signIn/credentials", {
body: { username: "user", password: "pass" },
searchParams: {
redirectTo: undefined,
},
})
expect(response).toEqual({ success: true, redirectURL: "/" })
})

test("signInCredentials with redirectTo option", async () => {
const post = vi.fn().mockResolvedValue(
createJSONResponse({
success: true,
redirectURL: "/dashboard",
})
)
createClientMock.mockReturnValue({
get: vi.fn(),
post,
})
const client = createAuthClient({ baseURL: "https://example.com" })
const response = await client.signInCredentials({
payload: { username: "user", password: "pass" },
redirectTo: "/dashboard",
})

expect(post).toHaveBeenCalledWith("/signIn/credentials", {
body: { username: "user", password: "pass" },
searchParams: {
redirectTo: "/dashboard",
},
})
expect(response).toEqual({ success: true, redirectURL: "/dashboard" })
})

test("signInCredentials with invalid credentials", async () => {
const post = vi.fn().mockResolvedValue(
createJSONResponse(
{
success: false,
redirectURL: null,
},
401
)
)
Comment thread
halvaradop marked this conversation as resolved.

createClientMock.mockReturnValue({
get: vi.fn(),
post,
})

const client = createAuthClient({ baseURL: "https://example.com" })
const response = await client.signInCredentials({ payload: { username: "user", password: "wrong_pass" } })

expect(post).toHaveBeenCalledWith("/signIn/credentials", {
body: { username: "user", password: "wrong_pass" },
searchParams: {
redirectTo: undefined,
},
})
expect(response).toEqual({ success: false, redirectURL: null })
})

test("updateSession without csrf token", async () => {
const get = vi.fn().mockResolvedValue(createJSONResponse({}, 500))
const patch = vi.fn()

createClientMock.mockReturnValue({
get,
patch,
})

const client = createAuthClient({ baseURL: "https://example.com" })
const response = await client.updateSession({
session: { user: { name: "Alice" }, expires: new Date().toISOString() },
})

expect(get).toHaveBeenCalledWith("/csrfToken")
expect(patch).not.toHaveBeenCalled()
expect(response).toEqual({ success: false, session: null })
})

test("updateSession with valid session", async () => {
const get = vi.fn().mockResolvedValue(createJSONResponse({ csrfToken: "csrf_token_1" }))

const patch = vi.fn().mockResolvedValue(
createJSONResponse({
success: true,
session: { user: { id: "user_1", name: "Alice" } },
})
)

createClientMock.mockReturnValue({
get,
patch,
})

const client = createAuthClient({ baseURL: "https://example.com" })
const expires = new Date(Date.now() + 60 * 60 * 1000)
const response = await client.updateSession({
session: { user: { name: "Alice" }, expires: expires.toISOString() },
})

expect(patch).toHaveBeenCalledWith("/session", {
body: {
user: { name: "Alice" },
expires,
},
headers: {
"X-CSRF-Token": "csrf_token_1",
},
})
expect(response).toEqual({ success: true, session: { user: { id: "user_1", name: "Alice" } } })
})

test("updateSession with no session", async () => {
const get = vi.fn().mockResolvedValue(createJSONResponse({ csrfToken: "csrf_token_1" }))
const patch = vi.fn()

createClientMock.mockReturnValue({
get,
patch,
})

const client = createAuthClient({ baseURL: "https://example.com" })
const response = await client.updateSession({ session: {} })
expect(response).toEqual({ success: false, session: null })
})

test("updateSession with redirectTo option", async () => {
const get = vi.fn().mockResolvedValue(createJSONResponse({ csrfToken: "csrf_token_1" }))

const patch = vi.fn().mockResolvedValue(
createJSONResponse({
success: true,
session: { user: { id: "user_1", name: "Alice" } },
redirectURL: "/dashboard",
})
)

createClientMock.mockReturnValue({
get,
patch,
})

const client = createAuthClient({ baseURL: "https://example.com" })
const expires = new Date(Math.floor(Date.now() / 1000) + 60 * 60 * 1000)
const response = await client.updateSession({
session: { user: { name: "Alice" }, expires: expires.toISOString() },
redirect: true,
redirectTo: "/dashboard",
})

expect(patch).toHaveBeenCalledWith("/session", {
body: {
user: { name: "Alice" },
expires,
},
headers: {
"X-CSRF-Token": "csrf_token_1",
},
})
expect(response).toEqual({ success: true, session: { user: { id: "user_1", name: "Alice" } }, redirectURL: "/dashboard" })
})

test("signOut", async () => {
const get = vi.fn().mockResolvedValue(createJSONResponse({ csrfToken: "csrf_token_1" }))
const post = vi.fn().mockResolvedValue(createJSONResponse({ redirect: true, url: "/logout" }, 202))
Expand Down
Loading