only show register if no users, otherwise show only login
This commit is contained in:
parent
daa1323b88
commit
3585ca70e8
6 changed files with 116 additions and 185 deletions
120
Cargo.lock
generated
120
Cargo.lock
generated
|
@ -606,16 +606,6 @@ dependencies = [
|
|||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
|
@ -843,21 +833,6 @@ version = "0.1.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
dependencies = [
|
||||
"foreign-types-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.1"
|
||||
|
@ -1463,23 +1438,6 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"openssl",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
|
@ -1568,50 +1526,6 @@ version = "1.20.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.68"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"openssl-macros",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.104"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "overload"
|
||||
version = "0.1.1"
|
||||
|
@ -1953,44 +1867,12 @@ dependencies = [
|
|||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.27"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.25"
|
||||
|
@ -2220,7 +2102,6 @@ dependencies = [
|
|||
"indexmap",
|
||||
"log",
|
||||
"memchr",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
|
@ -2741,7 +2622,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -13,15 +13,15 @@ jsonwebtoken = "9"
|
|||
actix-web = "4.4"
|
||||
actix-files = "0.6"
|
||||
actix-cors = "0.6"
|
||||
tokio = { version = "1.36", features = ["full"] }
|
||||
sqlx = { version = "0.8", features = ["runtime-tokio-native-tls", "postgres", "sqlite", "chrono"] }
|
||||
tokio = { version = "1.36", features = ["rt-multi-thread", "macros"] }
|
||||
sqlx = { version = "0.8", features = ["runtime-tokio", "postgres", "sqlite", "chrono"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
anyhow = "1.0"
|
||||
thiserror = "1.0"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
uuid = { version = "1.7", features = ["v4", "serde"] }
|
||||
uuid = { version = "1.7", features = ["v4"] } # Remove serde if not using UUID serialization
|
||||
base62 = "2.0"
|
||||
clap = { version = "4.5", features = ["derive"] }
|
||||
dotenv = "0.15"
|
||||
|
|
|
@ -72,4 +72,9 @@ export const getLinkSourceStats = async (id: number) => {
|
|||
return response.data;
|
||||
};
|
||||
|
||||
export const checkFirstUser = async () => {
|
||||
const response = await api.get<{ isFirstUser: boolean }>('/auth/check-first-user');
|
||||
return response.data.isFirstUser;
|
||||
};
|
||||
|
||||
export { api };
|
|
@ -1,4 +1,4 @@
|
|||
import { useState } from 'react'
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { z } from 'zod'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
|
@ -6,7 +6,6 @@ import { useAuth } from '../context/AuthContext'
|
|||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Card } from '@/components/ui/card'
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
|
@ -16,17 +15,18 @@ import {
|
|||
FormMessage,
|
||||
} from '@/components/ui/form'
|
||||
import { useToast } from '@/hooks/use-toast'
|
||||
import { checkFirstUser } from '../api/client'
|
||||
|
||||
const formSchema = z.object({
|
||||
email: z.string().email('Invalid email address'),
|
||||
password: z.string().min(6, 'Password must be at least 6 characters long'),
|
||||
adminToken: z.string(),
|
||||
adminToken: z.string().optional(),
|
||||
})
|
||||
|
||||
type FormValues = z.infer<typeof formSchema>
|
||||
|
||||
export function AuthForms() {
|
||||
const [activeTab, setActiveTab] = useState<'login' | 'register'>('login')
|
||||
const [isFirstUser, setIsFirstUser] = useState<boolean | null>(null)
|
||||
const { login, register } = useAuth()
|
||||
const { toast } = useToast()
|
||||
|
||||
|
@ -39,12 +39,26 @@ export function AuthForms() {
|
|||
},
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
const init = async () => {
|
||||
try {
|
||||
const isFirst = await checkFirstUser()
|
||||
setIsFirstUser(isFirst)
|
||||
} catch (err) {
|
||||
console.error('Error checking first user:', err)
|
||||
setIsFirstUser(false)
|
||||
}
|
||||
}
|
||||
|
||||
init()
|
||||
}, [])
|
||||
|
||||
const onSubmit = async (values: FormValues) => {
|
||||
try {
|
||||
if (activeTab === 'login') {
|
||||
await login(values.email, values.password)
|
||||
if (isFirstUser) {
|
||||
await register(values.email, values.password, values.adminToken || '')
|
||||
} else {
|
||||
await register(values.email, values.password, values.adminToken)
|
||||
await login(values.email, values.password)
|
||||
}
|
||||
form.reset()
|
||||
} catch (err: any) {
|
||||
|
@ -56,15 +70,23 @@ export function AuthForms() {
|
|||
}
|
||||
}
|
||||
|
||||
if (isFirstUser === null) {
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className="w-full max-w-md mx-auto p-6">
|
||||
<Tabs value={activeTab} onValueChange={(value: string) => setActiveTab(value as 'login' | 'register')}>
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="login">Login</TabsTrigger>
|
||||
<TabsTrigger value="register">Register</TabsTrigger>
|
||||
</TabsList>
|
||||
<div className="mb-6 text-center">
|
||||
<h2 className="text-2xl font-bold">
|
||||
{isFirstUser ? 'Create Admin Account' : 'Login'}
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{isFirstUser
|
||||
? 'Set up your admin account to get started'
|
||||
: 'Welcome back! Please login to your account'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<TabsContent value={activeTab}>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
|
||||
<FormField
|
||||
|
@ -95,7 +117,7 @@ export function AuthForms() {
|
|||
)}
|
||||
/>
|
||||
|
||||
{activeTab === 'register' && (
|
||||
{isFirstUser && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="adminToken"
|
||||
|
@ -112,12 +134,10 @@ export function AuthForms() {
|
|||
)}
|
||||
|
||||
<Button type="submit" className="w-full">
|
||||
{activeTab === 'login' ? 'Sign in' : 'Create account'}
|
||||
{isFirstUser ? 'Create Account' : 'Sign in'}
|
||||
</Button>
|
||||
</form>
|
||||
</Form>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ use argon2::{Argon2, PasswordHash, PasswordHasher};
|
|||
use jsonwebtoken::{encode, EncodingKey, Header};
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use serde_json::json;
|
||||
use sqlx::{Postgres, Sqlite};
|
||||
|
||||
lazy_static! {
|
||||
|
@ -690,3 +691,24 @@ pub async fn get_link_sources(
|
|||
|
||||
Ok(HttpResponse::Ok().json(sources))
|
||||
}
|
||||
|
||||
pub async fn check_first_user(state: web::Data<AppState>) -> Result<impl Responder, AppError> {
|
||||
let user_count = match &state.db {
|
||||
DatabasePool::Postgres(pool) => {
|
||||
sqlx::query_as::<Postgres, (i64,)>("SELECT COUNT(*)::bigint FROM users")
|
||||
.fetch_one(pool)
|
||||
.await?
|
||||
.0
|
||||
}
|
||||
DatabasePool::Sqlite(pool) => {
|
||||
sqlx::query_as::<Sqlite, (i64,)>("SELECT COUNT(*) FROM users")
|
||||
.fetch_one(pool)
|
||||
.await?
|
||||
.0
|
||||
}
|
||||
};
|
||||
|
||||
Ok(HttpResponse::Ok().json(json!({
|
||||
"isFirstUser": user_count == 0
|
||||
})))
|
||||
}
|
||||
|
|
|
@ -72,6 +72,10 @@ async fn main() -> Result<()> {
|
|||
)
|
||||
.route("/auth/register", web::post().to(handlers::register))
|
||||
.route("/auth/login", web::post().to(handlers::login))
|
||||
.route(
|
||||
"/auth/check-first-user",
|
||||
web::get().to(handlers::check_first_user),
|
||||
)
|
||||
.route("/health", web::get().to(handlers::health_check)),
|
||||
)
|
||||
.service(web::resource("/{short_code}").route(web::get().to(handlers::redirect_to_url)))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue