mirror of
https://github.com/fankes/beszel.git
synced 2025-10-18 17:29:28 +08:00
option to disable password login
This commit is contained in:
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
linguist-language=Go
|
30
main.go
30
main.go
@@ -23,7 +23,6 @@ import (
|
||||
"github.com/pocketbase/pocketbase/apis"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"github.com/pocketbase/pocketbase/plugins/migratecmd"
|
||||
"github.com/pocketbase/pocketbase/tools/cron"
|
||||
"github.com/pocketbase/pocketbase/tools/mailer"
|
||||
"golang.org/x/crypto/ssh"
|
||||
@@ -38,10 +37,31 @@ func main() {
|
||||
// loosely check if it was executed using "go run"
|
||||
isGoRun := strings.HasPrefix(os.Args[0], os.TempDir())
|
||||
|
||||
// enable auto creation of migration files when making collection changes in the Admin UI
|
||||
migratecmd.MustRegister(app, app.RootCmd, migratecmd.Config{
|
||||
// (the isGoRun check is to enable it only during development)
|
||||
Automigrate: isGoRun,
|
||||
// // enable auto creation of migration files when making collection changes in the Admin UI
|
||||
// migratecmd.MustRegister(app, app.RootCmd, migratecmd.Config{
|
||||
// // (the isGoRun check is to enable it only during development)
|
||||
// Automigrate: isGoRun,
|
||||
// })
|
||||
|
||||
// set auth settings
|
||||
app.OnAfterBootstrap().Add(func(e *core.BootstrapEvent) error {
|
||||
usersCollection, err := app.Dao().FindCollectionByNameOrId("users")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
usersAuthOptions := usersCollection.AuthOptions()
|
||||
if os.Getenv("DISABLE_PASSWORD_AUTH") == "true" {
|
||||
usersAuthOptions.AllowEmailAuth = false
|
||||
usersAuthOptions.AllowUsernameAuth = false
|
||||
} else {
|
||||
usersAuthOptions.AllowEmailAuth = true
|
||||
usersAuthOptions.AllowUsernameAuth = true
|
||||
}
|
||||
usersCollection.SetOptions(usersAuthOptions)
|
||||
if err := app.Dao().SaveCollection(usersCollection); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// serve site
|
||||
|
@@ -266,13 +266,13 @@ func init() {
|
||||
"options": {
|
||||
"allowEmailAuth": true,
|
||||
"allowOAuth2Auth": true,
|
||||
"allowUsernameAuth": true,
|
||||
"allowUsernameAuth": false,
|
||||
"exceptEmailDomains": null,
|
||||
"manageRule": null,
|
||||
"minPasswordLength": 8,
|
||||
"onlyEmailDomains": null,
|
||||
"onlyVerified": false,
|
||||
"requireEmail": false
|
||||
"onlyVerified": true,
|
||||
"requireEmail": true
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@@ -29,6 +29,12 @@ The hub and agent are distributed as single binary files, as well as docker imag
|
||||
|
||||
### Binary
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Name | Default | Description |
|
||||
| ----------------------- | ------- | ------------------------------------------------- |
|
||||
| `DISABLE_PASSWORD_AUTH` | unset | Disables password authentication if set to `true` |
|
||||
|
||||
## OAuth / OIDC integration
|
||||
|
||||
Beszel supports OpenID Connect and many OAuth2 authentication providers (see list below). To enable this, you will need to:
|
||||
@@ -64,7 +70,7 @@ Beszel supports OpenID Connect and many OAuth2 authentication providers (see lis
|
||||
- Yandex
|
||||
</details>
|
||||
|
||||
## API
|
||||
## REST API
|
||||
|
||||
Because Beszel is built on top of PocketBase, you can use the normal PocketBase API to read or update your data in your own applications.
|
||||
|
||||
|
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 75.7 81.8"><path fill="#22c55e" d="M76 29v14a33 33 0 0 1-1 7 30 30 0 0 1-1 2 29 29 0 0 1-3 7 27 27 0 0 1 0 1 27 27 0 0 1-6 6 27 27 0 0 1-7 4l14 12H54L42 72H29a32 32 0 0 1-8-1 29 29 0 0 1-3-1q-6-2-10-6a28 28 0 0 1-6-10 30 30 0 0 1-2-9 35 35 0 0 1 0-2V29a32 32 0 0 1 1-8 28 28 0 0 1 1-3 28 28 0 0 1 5-9 27 27 0 0 1 1-1 28 28 0 0 1 10-6 32 32 0 0 1 0 0 30 30 0 0 1 9-2 35 35 0 0 1 2 0h17a32 32 0 0 1 9 1 28 28 0 0 1 3 1 28 28 0 0 1 9 6 27 27 0 0 1 6 9 31 31 0 0 1 0 1 30 30 0 0 1 3 9 35 35 0 0 1 0 2ZM63 43V29a20 20 0 0 0 0-4 17 17 0 0 0-1-3 15 15 0 0 0-3-4 14 14 0 0 0-1-1 15 15 0 0 0-4-3 17 17 0 0 0-1 0 17 17 0 0 0-5-1 21 21 0 0 0-2 0H29a20 20 0 0 0-4 0 17 17 0 0 0-2 1l-6 3a15 15 0 0 0-3 5 17 17 0 0 0 0 0 17 17 0 0 0-1 5 22 22 0 0 0 0 2v14a20 20 0 0 0 0 4 17 17 0 0 0 1 2 15 15 0 0 0 3 5 14 14 0 0 0 0 1 15 15 0 0 0 5 3 17 17 0 0 0 1 0 17 17 0 0 0 4 1 21 21 0 0 0 2 0h17a20 20 0 0 0 4 0 17 17 0 0 0 3-1l5-3a15 15 0 0 0 4-5 17 17 0 0 0 0-1 17 17 0 0 0 1-4 22 22 0 0 0 0-2Z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 70 70"><path fill="#22c55e" d="M70 52.2v2.4a15.6 15.6 0 0 1-.3 2.8 20.5 20.5 0 0 1-.5 2.3q-.8 2.7-2.7 5a14 14 0 0 1-3.2 2.9 17.2 17.2 0 0 1-1.5.9q-3 1.5-7.2 1.5H6.4a6.7 6.7 0 0 1-2-.2 6.1 6.1 0 0 1-.5-.3 6.2 6.2 0 0 1-2-1.3 6.2 6.2 0 0 1-1.4-2A6.4 6.4 0 0 1 0 64a7.5 7.5 0 0 1 0-.4V6.4a6.4 6.4 0 0 1 .5-2.5Q1 2.7 1.8 2a6 6 0 0 1 2-1.4A6.4 6.4 0 0 1 6.2 0a7.5 7.5 0 0 1 .3 0h42.5a15.2 15.2 0 0 1 2.7.3 20 20 0 0 1 2.3.5q2.7.9 5 2.7a14 14 0 0 1 3 3.4 17 17 0 0 1 .9 1.4q1.5 2.9 1.5 7.1v2.4a23.2 23.2 0 0 1-.3 4 30.7 30.7 0 0 1-.8 3.3 23.9 23.9 0 0 1-3.5 7.1 26.8 26.8 0 0 1-.1.2 22.4 22.4 0 0 1 4 3.2 20.1 20.1 0 0 1 3 3.8 22.6 22.6 0 0 1 .3.5 21.5 21.5 0 0 1 1.7 3.6 26 26 0 0 1 .5 2 23.8 23.8 0 0 1 .7 3.9 30 30 0 0 1 .2 2.8Zm-12.7 2.3v-2.3a13.6 13.6 0 0 0-.2-2.5 10.7 10.7 0 0 0-.6-2 10 10 0 0 0-1.8-2.9 9.4 9.4 0 0 0-.4-.5 9.4 9.4 0 0 0-3.1-2 11 11 0 0 0-.3-.1 11.6 11.6 0 0 0-2.7-.7 14.7 14.7 0 0 0-1.8 0H17.8V28.5h22.9a14.1 14.1 0 0 0 2.5-.2 11.2 11.2 0 0 0 2-.5 9.7 9.7 0 0 0 2.7-1.6 9 9 0 0 0 .7-.6 9.5 9.5 0 0 0 2.1-3.3 11 11 0 0 0 0-.1 11.3 11.3 0 0 0 .7-2.6 14.6 14.6 0 0 0 .1-1.9v-2.4q0-2.7-2.6-2.7H12.7v44.6h41.9a6 6 0 0 0 .3 0h.5a2 2 0 0 0 .7-.2 2 2 0 0 0 .2-.1 1.5 1.5 0 0 0 .3-.3l.4-.5.3-1a6 6 0 0 0 0-.7Z"/></svg>
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 75.7 81.8"><path fill="#dc2626" d="M76 29v14a33 33 0 0 1-1 7 30 30 0 0 1-1 2 29 29 0 0 1-3 7 27 27 0 0 1 0 1 27 27 0 0 1-6 6 27 27 0 0 1-7 4l14 12H54L42 72H29a32 32 0 0 1-8-1 29 29 0 0 1-3-1q-6-2-10-6a28 28 0 0 1-6-10 30 30 0 0 1-2-9 35 35 0 0 1 0-2V29a32 32 0 0 1 1-8 28 28 0 0 1 1-3 28 28 0 0 1 5-9 27 27 0 0 1 1-1 28 28 0 0 1 10-6 32 32 0 0 1 0 0 30 30 0 0 1 9-2 35 35 0 0 1 2 0h17a32 32 0 0 1 9 1 28 28 0 0 1 3 1 28 28 0 0 1 9 6 27 27 0 0 1 6 9 31 31 0 0 1 0 1 30 30 0 0 1 3 9 35 35 0 0 1 0 2ZM63 43V29a20 20 0 0 0 0-4 17 17 0 0 0-1-3 15 15 0 0 0-3-4 14 14 0 0 0-1-1 15 15 0 0 0-4-3 17 17 0 0 0-1 0 17 17 0 0 0-5-1 21 21 0 0 0-2 0H29a20 20 0 0 0-4 0 17 17 0 0 0-2 1l-6 3a15 15 0 0 0-3 5 17 17 0 0 0 0 0 17 17 0 0 0-1 5 22 22 0 0 0 0 2v14a20 20 0 0 0 0 4 17 17 0 0 0 1 2 15 15 0 0 0 3 5 14 14 0 0 0 0 1 15 15 0 0 0 5 3 17 17 0 0 0 1 0 17 17 0 0 0 4 1 21 21 0 0 0 2 0h17a20 20 0 0 0 4 0 17 17 0 0 0 3-1l5-3a15 15 0 0 0 4-5 17 17 0 0 0 0-1 17 17 0 0 0 1-4 22 22 0 0 0 0-2Z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 70 70"><path fill="#dc2626" d="M70 52.2v2.4a15.6 15.6 0 0 1-.3 2.8 20.5 20.5 0 0 1-.5 2.3q-.8 2.7-2.7 5a14 14 0 0 1-3.2 2.9 17.2 17.2 0 0 1-1.5.9q-3 1.5-7.2 1.5H6.4a6.7 6.7 0 0 1-2-.2 6.1 6.1 0 0 1-.5-.3 6.2 6.2 0 0 1-2-1.3 6.2 6.2 0 0 1-1.4-2A6.4 6.4 0 0 1 0 64a7.5 7.5 0 0 1 0-.4V6.4a6.4 6.4 0 0 1 .5-2.5Q1 2.7 1.8 2a6 6 0 0 1 2-1.4A6.4 6.4 0 0 1 6.2 0a7.5 7.5 0 0 1 .3 0h42.5a15.2 15.2 0 0 1 2.7.3 20 20 0 0 1 2.3.5q2.7.9 5 2.7a14 14 0 0 1 3 3.4 17 17 0 0 1 .9 1.4q1.5 2.9 1.5 7.1v2.4a23.2 23.2 0 0 1-.3 4 30.7 30.7 0 0 1-.8 3.3 23.9 23.9 0 0 1-3.5 7.1 26.8 26.8 0 0 1-.1.2 22.4 22.4 0 0 1 4 3.2 20.1 20.1 0 0 1 3 3.8 22.6 22.6 0 0 1 .3.5 21.5 21.5 0 0 1 1.7 3.6 26 26 0 0 1 .5 2 23.8 23.8 0 0 1 .7 3.9 30 30 0 0 1 .2 2.8Zm-12.7 2.3v-2.3a13.6 13.6 0 0 0-.2-2.5 10.7 10.7 0 0 0-.6-2 10 10 0 0 0-1.8-2.9 9.4 9.4 0 0 0-.4-.5 9.4 9.4 0 0 0-3.1-2 11 11 0 0 0-.3-.1 11.6 11.6 0 0 0-2.7-.7 14.7 14.7 0 0 0-1.8 0H17.8V28.5h22.9a14.1 14.1 0 0 0 2.5-.2 11.2 11.2 0 0 0 2-.5 9.7 9.7 0 0 0 2.7-1.6 9 9 0 0 0 .7-.6 9.5 9.5 0 0 0 2.1-3.3 11 11 0 0 0 0-.1 11.3 11.3 0 0 0 .7-2.6 14.6 14.6 0 0 0 .1-1.9v-2.4q0-2.7-2.6-2.7H12.7v44.6h41.9a6 6 0 0 0 .3 0h.5a2 2 0 0 0 .7-.2 2 2 0 0 0 .2-.1 1.5 1.5 0 0 0 .3-.3l.4-.5.3-1a6 6 0 0 0 0-.7Z"/></svg>
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 75.7 81.8"><path fill="#888" d="M76 29v14a33 33 0 0 1-1 7 30 30 0 0 1-1 2 29 29 0 0 1-3 7 27 27 0 0 1 0 1 27 27 0 0 1-6 6 27 27 0 0 1-7 4l14 12H54L42 72H29a32 32 0 0 1-8-1 29 29 0 0 1-3-1q-6-2-10-6a28 28 0 0 1-6-10 30 30 0 0 1-2-9 35 35 0 0 1 0-2V29a32 32 0 0 1 1-8 28 28 0 0 1 1-3 28 28 0 0 1 5-9 27 27 0 0 1 1-1 28 28 0 0 1 10-6 32 32 0 0 1 0 0 30 30 0 0 1 9-2 35 35 0 0 1 2 0h17a32 32 0 0 1 9 1 28 28 0 0 1 3 1 28 28 0 0 1 9 6 27 27 0 0 1 6 9 31 31 0 0 1 0 1 30 30 0 0 1 3 9 35 35 0 0 1 0 2ZM63 43V29a20 20 0 0 0 0-4 17 17 0 0 0-1-3 15 15 0 0 0-3-4 14 14 0 0 0-1-1 15 15 0 0 0-4-3 17 17 0 0 0-1 0 17 17 0 0 0-5-1 21 21 0 0 0-2 0H29a20 20 0 0 0-4 0 17 17 0 0 0-2 1l-6 3a15 15 0 0 0-3 5 17 17 0 0 0 0 0 17 17 0 0 0-1 5 22 22 0 0 0 0 2v14a20 20 0 0 0 0 4 17 17 0 0 0 1 2 15 15 0 0 0 3 5 14 14 0 0 0 0 1 15 15 0 0 0 5 3 17 17 0 0 0 1 0 17 17 0 0 0 4 1 21 21 0 0 0 2 0h17a20 20 0 0 0 4 0 17 17 0 0 0 3-1l5-3a15 15 0 0 0 4-5 17 17 0 0 0 0-1 17 17 0 0 0 1-4 22 22 0 0 0 0-2Z"/></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 70 70"><path fill="#888" d="M70 52.2v2.4a15.6 15.6 0 0 1-.3 2.8 20.5 20.5 0 0 1-.5 2.3q-.8 2.7-2.7 5a14 14 0 0 1-3.2 2.9 17.2 17.2 0 0 1-1.5.9q-3 1.5-7.2 1.5H6.4a6.7 6.7 0 0 1-2-.2 6.1 6.1 0 0 1-.5-.3 6.2 6.2 0 0 1-2-1.3 6.2 6.2 0 0 1-1.4-2A6.4 6.4 0 0 1 0 64a7.5 7.5 0 0 1 0-.4V6.4a6.4 6.4 0 0 1 .5-2.5Q1 2.7 1.8 2a6 6 0 0 1 2-1.4A6.4 6.4 0 0 1 6.2 0a7.5 7.5 0 0 1 .3 0h42.5a15.2 15.2 0 0 1 2.7.3 20 20 0 0 1 2.3.5q2.7.9 5 2.7a14 14 0 0 1 3 3.4 17 17 0 0 1 .9 1.4q1.5 2.9 1.5 7.1v2.4a23.2 23.2 0 0 1-.3 4 30.7 30.7 0 0 1-.8 3.3 23.9 23.9 0 0 1-3.5 7.1 26.8 26.8 0 0 1-.1.2 22.4 22.4 0 0 1 4 3.2 20.1 20.1 0 0 1 3 3.8 22.6 22.6 0 0 1 .3.5 21.5 21.5 0 0 1 1.7 3.6 26 26 0 0 1 .5 2 23.8 23.8 0 0 1 .7 3.9 30 30 0 0 1 .2 2.8Zm-12.7 2.3v-2.3a13.6 13.6 0 0 0-.2-2.5 10.7 10.7 0 0 0-.6-2 10 10 0 0 0-1.8-2.9 9.4 9.4 0 0 0-.4-.5 9.4 9.4 0 0 0-3.1-2 11 11 0 0 0-.3-.1 11.6 11.6 0 0 0-2.7-.7 14.7 14.7 0 0 0-1.8 0H17.8V28.5h22.9a14.1 14.1 0 0 0 2.5-.2 11.2 11.2 0 0 0 2-.5 9.7 9.7 0 0 0 2.7-1.6 9 9 0 0 0 .7-.6 9.5 9.5 0 0 0 2.1-3.3 11 11 0 0 0 0-.1 11.3 11.3 0 0 0 .7-2.6 14.6 14.6 0 0 0 .1-1.9v-2.4q0-2.7-2.6-2.7H12.7v44.6h41.9a6 6 0 0 0 .3 0h.5a2 2 0 0 0 .7-.2 2 2 0 0 0 .2-.1 1.5 1.5 0 0 0 .3-.3l.4-.5.3-1a6 6 0 0 0 0-.7Z"/></svg>
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -10,12 +10,11 @@ import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogTrigger,
|
||||
DialogDescription,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { AuthProviderInfo } from 'pocketbase'
|
||||
import { useState } from 'react'
|
||||
import { AuthMethodsList } from 'pocketbase'
|
||||
import { Link } from '../router'
|
||||
|
||||
const honeypot = v.literal('')
|
||||
@@ -57,26 +56,16 @@ const showLoginFaliedToast = () => {
|
||||
export function UserAuthForm({
|
||||
className,
|
||||
isFirstRun,
|
||||
authMethods,
|
||||
...props
|
||||
}: {
|
||||
className?: string
|
||||
isFirstRun: boolean
|
||||
authMethods: AuthMethodsList
|
||||
}) {
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false)
|
||||
const [isGitHubLoading, setIsOauthLoading] = useState<boolean>(false)
|
||||
const [errors, setErrors] = useState<Record<string, string | undefined>>({})
|
||||
const [authProviders, setAuthProviders] = useState<AuthProviderInfo[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
pb.collection('users')
|
||||
.listAuthMethods()
|
||||
.then((methods) => {
|
||||
console.log('methods', methods)
|
||||
console.log('password active', methods.emailPassword)
|
||||
setAuthProviders(methods.authProviders)
|
||||
console.log('auth providers', authProviders)
|
||||
})
|
||||
}, [])
|
||||
|
||||
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||||
e.preventDefault()
|
||||
@@ -130,120 +119,133 @@ export function UserAuthForm({
|
||||
}
|
||||
}
|
||||
|
||||
if (!authMethods) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn('grid gap-6', className)} {...props}>
|
||||
<form onSubmit={handleSubmit} onChange={() => setErrors({})}>
|
||||
<div className="grid gap-2.5">
|
||||
{isFirstRun && (
|
||||
<div className="grid gap-1 relative">
|
||||
<UserIcon className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
|
||||
<Label className="sr-only" htmlFor="username">
|
||||
Username
|
||||
</Label>
|
||||
<Input
|
||||
autoFocus={true}
|
||||
id="username"
|
||||
name="username"
|
||||
required
|
||||
placeholder="username"
|
||||
type="username"
|
||||
autoCapitalize="none"
|
||||
autoComplete="username"
|
||||
autoCorrect="off"
|
||||
disabled={isLoading || isGitHubLoading}
|
||||
className="pl-9"
|
||||
/>
|
||||
{errors?.username && <p className="px-1 text-xs text-red-600">{errors.username}</p>}
|
||||
</div>
|
||||
)}
|
||||
<div className="grid gap-1 relative">
|
||||
<MailIcon className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
|
||||
<Label className="sr-only" htmlFor="email">
|
||||
Email
|
||||
</Label>
|
||||
<Input
|
||||
id="email"
|
||||
name="email"
|
||||
required
|
||||
placeholder={isFirstRun ? 'email' : 'name@example.com'}
|
||||
type="email"
|
||||
autoCapitalize="none"
|
||||
autoComplete="email"
|
||||
autoCorrect="off"
|
||||
disabled={isLoading || isGitHubLoading}
|
||||
className="pl-9"
|
||||
/>
|
||||
{errors?.email && <p className="px-1 text-xs text-red-600">{errors.email}</p>}
|
||||
</div>
|
||||
<div className="grid gap-1 relative">
|
||||
<LockIcon className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
|
||||
<Label className="sr-only" htmlFor="pass">
|
||||
Password
|
||||
</Label>
|
||||
<Input
|
||||
id="pass"
|
||||
name="password"
|
||||
placeholder="password"
|
||||
required
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
disabled={isLoading || isGitHubLoading}
|
||||
className="pl-9"
|
||||
/>
|
||||
{errors?.password && <p className="px-1 text-xs text-red-600">{errors.password}</p>}
|
||||
</div>
|
||||
{isFirstRun && (
|
||||
<div className="grid gap-1 relative">
|
||||
<LockIcon className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
|
||||
<Label className="sr-only" htmlFor="pass2">
|
||||
Confirm password
|
||||
</Label>
|
||||
<Input
|
||||
id="pass2"
|
||||
name="passwordConfirm"
|
||||
placeholder="confirm password"
|
||||
required
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
disabled={isLoading || isGitHubLoading}
|
||||
className="pl-9"
|
||||
/>
|
||||
{errors?.passwordConfirm && (
|
||||
<p className="px-1 text-xs text-red-600">{errors.passwordConfirm}</p>
|
||||
{authMethods.emailPassword && (
|
||||
<>
|
||||
<form onSubmit={handleSubmit} onChange={() => setErrors({})}>
|
||||
<div className="grid gap-2.5">
|
||||
{isFirstRun && (
|
||||
<div className="grid gap-1 relative">
|
||||
<UserIcon className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
|
||||
<Label className="sr-only" htmlFor="username">
|
||||
Username
|
||||
</Label>
|
||||
<Input
|
||||
autoFocus={true}
|
||||
id="username"
|
||||
name="username"
|
||||
required
|
||||
placeholder="username"
|
||||
type="username"
|
||||
autoCapitalize="none"
|
||||
autoComplete="username"
|
||||
autoCorrect="off"
|
||||
disabled={isLoading || isGitHubLoading}
|
||||
className="pl-9"
|
||||
/>
|
||||
{errors?.username && (
|
||||
<p className="px-1 text-xs text-red-600">{errors.username}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="grid gap-1 relative">
|
||||
<MailIcon className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
|
||||
<Label className="sr-only" htmlFor="email">
|
||||
Email
|
||||
</Label>
|
||||
<Input
|
||||
id="email"
|
||||
name="email"
|
||||
required
|
||||
placeholder={isFirstRun ? 'email' : 'name@example.com'}
|
||||
type="email"
|
||||
autoCapitalize="none"
|
||||
autoComplete="email"
|
||||
autoCorrect="off"
|
||||
disabled={isLoading || isGitHubLoading}
|
||||
className="pl-9"
|
||||
/>
|
||||
{errors?.email && <p className="px-1 text-xs text-red-600">{errors.email}</p>}
|
||||
</div>
|
||||
<div className="grid gap-1 relative">
|
||||
<LockIcon className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
|
||||
<Label className="sr-only" htmlFor="pass">
|
||||
Password
|
||||
</Label>
|
||||
<Input
|
||||
id="pass"
|
||||
name="password"
|
||||
placeholder="password"
|
||||
required
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
disabled={isLoading || isGitHubLoading}
|
||||
className="pl-9"
|
||||
/>
|
||||
{errors?.password && <p className="px-1 text-xs text-red-600">{errors.password}</p>}
|
||||
</div>
|
||||
{isFirstRun && (
|
||||
<div className="grid gap-1 relative">
|
||||
<LockIcon className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
|
||||
<Label className="sr-only" htmlFor="pass2">
|
||||
Confirm password
|
||||
</Label>
|
||||
<Input
|
||||
id="pass2"
|
||||
name="passwordConfirm"
|
||||
placeholder="confirm password"
|
||||
required
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
disabled={isLoading || isGitHubLoading}
|
||||
className="pl-9"
|
||||
/>
|
||||
{errors?.passwordConfirm && (
|
||||
<p className="px-1 text-xs text-red-600">{errors.passwordConfirm}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="sr-only">
|
||||
{/* honeypot */}
|
||||
<label htmlFor="name"></label>
|
||||
<input id="name" type="text" name="name" tabIndex={-1} />
|
||||
</div>
|
||||
<button className={cn(buttonVariants())} disabled={isLoading}>
|
||||
{isLoading ? (
|
||||
<LoaderCircle className="mr-2 h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<LogInIcon className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
{isFirstRun ? 'Create account' : 'Sign in'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<span className="w-full border-t" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-xs uppercase">
|
||||
<span className="bg-background px-2 text-muted-foreground">Or continue with</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="sr-only">
|
||||
{/* honeypot */}
|
||||
<label htmlFor="name"></label>
|
||||
<input id="name" type="text" name="name" tabIndex={-1} />
|
||||
</div>
|
||||
<button className={cn(buttonVariants())} disabled={isLoading}>
|
||||
{isLoading ? (
|
||||
<LoaderCircle className="mr-2 h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<LogInIcon className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
{isFirstRun ? 'Create account' : 'Sign in'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 flex items-center">
|
||||
<span className="w-full border-t" />
|
||||
</div>
|
||||
<div className="relative flex justify-center text-xs uppercase">
|
||||
<span className="bg-background px-2 text-muted-foreground">Or continue with</span>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{authProviders.length > 0 && (
|
||||
<div className="grid gap-2">
|
||||
{authProviders.map((provider) => (
|
||||
{authMethods.authProviders.length > 0 && (
|
||||
<div className="grid gap-2 -mt-1">
|
||||
{authMethods.authProviders.map((provider) => (
|
||||
<button
|
||||
key={provider.name}
|
||||
type="button"
|
||||
className={cn(buttonVariants({ variant: 'outline' }))}
|
||||
className={cn(buttonVariants({ variant: 'outline' }), {
|
||||
'justify-self-center': !authMethods.emailPassword,
|
||||
'px-5': !authMethods.emailPassword,
|
||||
})}
|
||||
onClick={async () => {
|
||||
setIsOauthLoading(true)
|
||||
try {
|
||||
@@ -275,7 +277,7 @@ export function UserAuthForm({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!authProviders.length && (
|
||||
{!authMethods.authProviders.length && (
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<button type="button" className={cn(buttonVariants({ variant: 'outline' }))}>
|
||||
@@ -303,12 +305,15 @@ export function UserAuthForm({
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)}
|
||||
<Link
|
||||
href="/forgot-password"
|
||||
className="text-sm mx-auto mt-2 hover:text-brand underline underline-offset-4 opacity-70 hover:opacity-100 transition-opacity"
|
||||
>
|
||||
Forgot password?
|
||||
</Link>
|
||||
|
||||
{authMethods.emailPassword && (
|
||||
<Link
|
||||
href="/forgot-password"
|
||||
className="text-sm mx-auto hover:text-brand underline underline-offset-4 opacity-70 hover:opacity-100 transition-opacity"
|
||||
>
|
||||
Forgot password?
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { LoaderCircle, MailIcon, SendIcon } from 'lucide-react'
|
||||
import { LoaderCircle, MailIcon, SendHorizonalIcon } from 'lucide-react'
|
||||
import { Input } from '../ui/input'
|
||||
import { Label } from '../ui/label'
|
||||
import { useCallback, useState } from 'react'
|
||||
@@ -70,7 +70,7 @@ export default function ForgotPassword() {
|
||||
{isLoading ? (
|
||||
<LoaderCircle className="mr-2 h-4 w-4 animate-spin" />
|
||||
) : (
|
||||
<SendIcon className="mr-2 h-4 w-4" />
|
||||
<SendHorizonalIcon className="mr-2 h-4 w-4" />
|
||||
)}
|
||||
Reset password
|
||||
</button>
|
||||
@@ -78,7 +78,7 @@ export default function ForgotPassword() {
|
||||
</form>
|
||||
<Dialog>
|
||||
<DialogTrigger asChild>
|
||||
<button className="text-sm mx-auto mt-2 hover:text-brand underline underline-offset-4 opacity-70 hover:opacity-100 transition-opacity">
|
||||
<button className="text-sm mx-auto hover:text-brand underline underline-offset-4 opacity-70 hover:opacity-100 transition-opacity">
|
||||
Command line instructions
|
||||
</button>
|
||||
</DialogTrigger>
|
||||
|
@@ -5,10 +5,12 @@ import { pb } from '@/lib/stores'
|
||||
import { useStore } from '@nanostores/react'
|
||||
import ForgotPassword from './forgot-pass-form'
|
||||
import { $router } from '../router'
|
||||
import { AuthMethodsList } from 'pocketbase'
|
||||
|
||||
export default function () {
|
||||
const page = useStore($router)
|
||||
const [isFirstRun, setFirstRun] = useState(false)
|
||||
const [authMethods, setAuthMethods] = useState<AuthMethodsList>()
|
||||
|
||||
useEffect(() => {
|
||||
document.title = 'Login / Beszel'
|
||||
@@ -18,6 +20,14 @@ export default function () {
|
||||
})
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
pb.collection('users')
|
||||
.listAuthMethods()
|
||||
.then((methods) => {
|
||||
setAuthMethods(methods)
|
||||
})
|
||||
}, [])
|
||||
|
||||
const subtitle = useMemo(() => {
|
||||
if (isFirstRun) {
|
||||
return 'Please create an admin account'
|
||||
@@ -28,9 +38,13 @@ export default function () {
|
||||
}
|
||||
}, [isFirstRun, page])
|
||||
|
||||
if (!authMethods) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen grid items-center py-12">
|
||||
<div className="grid gap-5 w-full px-4 max-w-[22em] mx-auto">
|
||||
<div className="grid gap-5 w-full px-4 mx-auto" style={{ maxWidth: '22em' }}>
|
||||
<div className="text-center">
|
||||
<h1 className="mb-3">
|
||||
<Logo className="h-7 fill-foreground mx-auto" />
|
||||
@@ -41,7 +55,7 @@ export default function () {
|
||||
{page?.path === '/forgot-password' ? (
|
||||
<ForgotPassword />
|
||||
) : (
|
||||
<UserAuthForm isFirstRun={isFirstRun} />
|
||||
<UserAuthForm isFirstRun={isFirstRun} authMethods={authMethods} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user