Skip to content

Commit

Permalink
feat: admin auth through kratos (#1278)
Browse files Browse the repository at this point in the history
* chore: cleanup docker compose

chore: update kratos

* feat: user onboarding module

chore: config passed on

chore: saving the user

chore: remove auth handler

feat: e2e working login logout

chore: compile

* chore: remove next and pg

* test: fix e2e

test(e2e): helpers

save

fix: csr bailout and direct link

fix: cypress

test: with headless

test?

fix: probably cookies passing works

test: run

test

save

* test: huge expiration date

* chore: sid comments

* chore: suspense boundary

* fix: flickering

* fix: chekc-code

* fix: use window.localstorage instead

* fix: session for e2e test

* fix: layout flicker

* chore: move up bp

* chore: jc comments

* chore: auth guard
  • Loading branch information
sandipndev authored Jan 31, 2025
1 parent 7363247 commit 165b298
Show file tree
Hide file tree
Showing 82 changed files with 1,038 additions and 1,520 deletions.
12 changes: 8 additions & 4 deletions .github/workflows/cypress.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ name: cypress

on:
pull_request:
branches: [ main ]
paths: [ "apps/admin-panel/**" ]
branches: [main]
paths: ["apps/admin-panel/**"]
push:
branches: [ main ]
paths: [ "apps/admin-panel/**" ]
branches: [main]
paths: ["apps/admin-panel/**"]
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
cypress-test-run-in-browserstack:
name: browserstack / local tunnel into tilt up
Expand Down
25 changes: 25 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ members = [
"lana/events",
"lana/ids",
"lana/dashboard",
"lana/user-onboarding",

"core/user",
"core/governance",
Expand Down
1 change: 1 addition & 0 deletions apps/admin-panel/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ results
build_artifacts
screenshots
downloads
videos
60 changes: 0 additions & 60 deletions apps/admin-panel/app/api/auth/[...nextauth]/options.ts

This file was deleted.

6 changes: 0 additions & 6 deletions apps/admin-panel/app/api/auth/[...nextauth]/route.ts

This file was deleted.

45 changes: 27 additions & 18 deletions apps/admin-panel/app/app-layout.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,39 @@
"use client"

import { CommandMenu } from "./command-menu"
import CreateButton, { CreateContextProvider } from "./create"
import { BreadcrumbProvider } from "./breadcrumb-provider"
import { DynamicBreadcrumb } from "./dynamic-breadcrumb"

import { AppSidebar } from "@/components/app-sidebar"
import { RealtimePriceUpdates } from "@/components/realtime-price"
import { SidebarTrigger } from "@/ui/sidebar"
import { SidebarInset, SidebarProvider, SidebarTrigger } from "@/ui/sidebar"

import { env } from "@/env"

export const AppLayout = ({ children }: Readonly<{ children: React.ReactNode }>) => {
const appVersion = env.NEXT_PUBLIC_APP_VERSION

return (
<BreadcrumbProvider>
<CreateContextProvider>
<div className="container mx-auto p-2">
<div className="max-w-7xl w-full mx-auto">
<header className="flex justify-between items-center mb-2 align-middle">
<div className="flex items-center gap-2">
<SidebarTrigger className="md:hidden" />
<DynamicBreadcrumb />
</div>
<CreateButton />
</header>
<RealtimePriceUpdates />
<main>{children}</main>
<CreateContextProvider>
<SidebarProvider>
<AppSidebar appVersion={appVersion} />
<SidebarInset className="min-h-screen md:peer-data-[variant=inset]:shadow-none border">
<CommandMenu />
<div className="container mx-auto p-2">
<div className="max-w-7xl w-full mx-auto">
<header className="flex justify-between items-center mb-2 align-middle">
<div className="flex items-center gap-2">
<SidebarTrigger className="md:hidden" />
<DynamicBreadcrumb />
</div>
<CreateButton />
</header>
<RealtimePriceUpdates />
<main>{children}</main>
</div>
</div>
</div>
</CreateContextProvider>
</BreadcrumbProvider>
</SidebarInset>
</SidebarProvider>
</CreateContextProvider>
)
}
37 changes: 24 additions & 13 deletions apps/admin-panel/app/auth/login/page.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
"use client"

import { useState, useEffect } from "react"
import { getCsrfToken } from "next-auth/react"
import { useState } from "react"
import { useRouter } from "next/navigation"

import { loginUser } from "../ory"

import { Input } from "@/components/input"
import { basePath } from "@/env"
import { Button } from "@/ui/button"

const Login: React.FC = () => {
const [csrfToken, setCsrfToken] = useState<string | null>(null)
useEffect(() => {
getCsrfToken().then((token) => token && setCsrfToken(token))
})
const router = useRouter()

const [email, setEmail] = useState("")
const [error, setError] = useState("")

const onSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault()
setError("")

try {
const flowId = await loginUser(email)
router.push(`/auth/verify?flow=${flowId}`)
} catch {
setError("Please check your credentials and try again.")
}
}

return (
<>
Expand All @@ -20,20 +33,18 @@ const Login: React.FC = () => {
<div className="text-md">Welcome to Lana Bank Admin Panel</div>
<div className="text-md font-light">Enter your email address to continue</div>
</div>
<form
className="space-y-[20px] w-full"
action={`${basePath}/api/auth/signin/email`}
method="POST"
>
<input name="csrfToken" type="hidden" defaultValue={csrfToken || ""} />
<form className="space-y-[20px] w-full" onSubmit={onSubmit}>
<Input
label="Your email"
type="email"
name="email"
autofocus
placeholder="Please enter your email address"
defaultValue={email}
onChange={setEmail}
/>
<Button type="submit">Submit</Button>
{error && <div className="text-destructive">{error}</div>}
</form>
</>
)
Expand Down
106 changes: 106 additions & 0 deletions apps/admin-panel/app/auth/ory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
"use client"

/* eslint-disable camelcase */ // Many Ory Kratos request body params are snake_case

import { Configuration, FrontendApi, UiNodeInputAttributes } from "@ory/client"
import axios, { AxiosError } from "axios"

import { basePath } from "@/env"

export const getOryClient = () =>
new FrontendApi(
new Configuration({
basePath,
baseOptions: {
withCredentials: true,
timeout: 10000,
},
}),
"",
axios,
)

export const getSession = () => {
const kratos = getOryClient()
return kratos.toSession()
}

export const loginUser = async (email: string) => {
const oryClient = getOryClient()
let flowId: string = ""

try {
const { data: flow } = await oryClient.createBrowserLoginFlow()
flowId = flow.id

const { data: loginData } = await oryClient.updateLoginFlow({
flow: flow.id,
updateLoginFlowBody: {
method: "code",
identifier: email,
csrf_token:
(
flow.ui.nodes.find(
(node) =>
node.attributes.node_type === "input" &&
node.attributes.name === "csrf_token",
)?.attributes as UiNodeInputAttributes
).value || "",
},
})
return loginData
} catch (error) {
if (
error instanceof AxiosError &&
error.code === AxiosError.ERR_BAD_REQUEST &&
error.response?.data.ui.messages[0].id === 1010014
) {
return flowId
}
throw error
}
}

export const loginUserWithOtp = async (flowId: string, otp: string) => {
const oryClient = getOryClient()

const { data: loginFlow } = await oryClient.getLoginFlow({
id: flowId,
})

const csrf_token =
(
loginFlow.ui.nodes.find(
(node) =>
node.attributes.node_type === "input" && node.attributes.name === "csrf_token",
)?.attributes as UiNodeInputAttributes
).value || ""

const identifier =
(
loginFlow.ui.nodes.find(
(node) =>
node.attributes.node_type === "input" && node.attributes.name === "identifier",
)?.attributes as UiNodeInputAttributes
).value || ""

const { data: loginData } = await oryClient.updateLoginFlow({
flow: flowId,
updateLoginFlowBody: {
method: "code",
identifier,
csrf_token,
code: otp,
},
})

return loginData
}

export const logoutUser = async () => {
const oryClient = getOryClient()
const { data } = await oryClient.createBrowserLogoutFlow()
await oryClient.updateLogoutFlow({
token: data.logout_token,
})
}
Loading

0 comments on commit 165b298

Please sign in to comment.