mirror of
https://github.com/fankes/beszel.git
synced 2025-12-13 09:41:00 +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/apis"
|
||||||
"github.com/pocketbase/pocketbase/core"
|
"github.com/pocketbase/pocketbase/core"
|
||||||
"github.com/pocketbase/pocketbase/models"
|
"github.com/pocketbase/pocketbase/models"
|
||||||
"github.com/pocketbase/pocketbase/plugins/migratecmd"
|
|
||||||
"github.com/pocketbase/pocketbase/tools/cron"
|
"github.com/pocketbase/pocketbase/tools/cron"
|
||||||
"github.com/pocketbase/pocketbase/tools/mailer"
|
"github.com/pocketbase/pocketbase/tools/mailer"
|
||||||
"golang.org/x/crypto/ssh"
|
"golang.org/x/crypto/ssh"
|
||||||
@@ -38,10 +37,31 @@ func main() {
|
|||||||
// loosely check if it was executed using "go run"
|
// loosely check if it was executed using "go run"
|
||||||
isGoRun := strings.HasPrefix(os.Args[0], os.TempDir())
|
isGoRun := strings.HasPrefix(os.Args[0], os.TempDir())
|
||||||
|
|
||||||
// enable auto creation of migration files when making collection changes in the Admin UI
|
// // enable auto creation of migration files when making collection changes in the Admin UI
|
||||||
migratecmd.MustRegister(app, app.RootCmd, migratecmd.Config{
|
// migratecmd.MustRegister(app, app.RootCmd, migratecmd.Config{
|
||||||
// (the isGoRun check is to enable it only during development)
|
// // (the isGoRun check is to enable it only during development)
|
||||||
Automigrate: isGoRun,
|
// 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
|
// serve site
|
||||||
|
|||||||
@@ -266,13 +266,13 @@ func init() {
|
|||||||
"options": {
|
"options": {
|
||||||
"allowEmailAuth": true,
|
"allowEmailAuth": true,
|
||||||
"allowOAuth2Auth": true,
|
"allowOAuth2Auth": true,
|
||||||
"allowUsernameAuth": true,
|
"allowUsernameAuth": false,
|
||||||
"exceptEmailDomains": null,
|
"exceptEmailDomains": null,
|
||||||
"manageRule": null,
|
"manageRule": null,
|
||||||
"minPasswordLength": 8,
|
"minPasswordLength": 8,
|
||||||
"onlyEmailDomains": null,
|
"onlyEmailDomains": null,
|
||||||
"onlyVerified": false,
|
"onlyVerified": true,
|
||||||
"requireEmail": false
|
"requireEmail": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -29,6 +29,12 @@ The hub and agent are distributed as single binary files, as well as docker imag
|
|||||||
|
|
||||||
### Binary
|
### Binary
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
| Name | Default | Description |
|
||||||
|
| ----------------------- | ------- | ------------------------------------------------- |
|
||||||
|
| `DISABLE_PASSWORD_AUTH` | unset | Disables password authentication if set to `true` |
|
||||||
|
|
||||||
## OAuth / OIDC integration
|
## OAuth / OIDC integration
|
||||||
|
|
||||||
Beszel supports OpenID Connect and many OAuth2 authentication providers (see list below). To enable this, you will need to:
|
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
|
- Yandex
|
||||||
</details>
|
</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.
|
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,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
DialogDescription,
|
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
} from '@/components/ui/dialog'
|
} from '@/components/ui/dialog'
|
||||||
import { useEffect, useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { AuthProviderInfo } from 'pocketbase'
|
import { AuthMethodsList } from 'pocketbase'
|
||||||
import { Link } from '../router'
|
import { Link } from '../router'
|
||||||
|
|
||||||
const honeypot = v.literal('')
|
const honeypot = v.literal('')
|
||||||
@@ -57,26 +56,16 @@ const showLoginFaliedToast = () => {
|
|||||||
export function UserAuthForm({
|
export function UserAuthForm({
|
||||||
className,
|
className,
|
||||||
isFirstRun,
|
isFirstRun,
|
||||||
|
authMethods,
|
||||||
...props
|
...props
|
||||||
}: {
|
}: {
|
||||||
className?: string
|
className?: string
|
||||||
isFirstRun: boolean
|
isFirstRun: boolean
|
||||||
|
authMethods: AuthMethodsList
|
||||||
}) {
|
}) {
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false)
|
const [isLoading, setIsLoading] = useState<boolean>(false)
|
||||||
const [isGitHubLoading, setIsOauthLoading] = useState<boolean>(false)
|
const [isGitHubLoading, setIsOauthLoading] = useState<boolean>(false)
|
||||||
const [errors, setErrors] = useState<Record<string, string | undefined>>({})
|
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>) {
|
async function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@@ -130,120 +119,133 @@ export function UserAuthForm({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!authMethods) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cn('grid gap-6', className)} {...props}>
|
<div className={cn('grid gap-6', className)} {...props}>
|
||||||
<form onSubmit={handleSubmit} onChange={() => setErrors({})}>
|
{authMethods.emailPassword && (
|
||||||
<div className="grid gap-2.5">
|
<>
|
||||||
{isFirstRun && (
|
<form onSubmit={handleSubmit} onChange={() => setErrors({})}>
|
||||||
<div className="grid gap-1 relative">
|
<div className="grid gap-2.5">
|
||||||
<UserIcon className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
|
{isFirstRun && (
|
||||||
<Label className="sr-only" htmlFor="username">
|
<div className="grid gap-1 relative">
|
||||||
Username
|
<UserIcon className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
|
||||||
</Label>
|
<Label className="sr-only" htmlFor="username">
|
||||||
<Input
|
Username
|
||||||
autoFocus={true}
|
</Label>
|
||||||
id="username"
|
<Input
|
||||||
name="username"
|
autoFocus={true}
|
||||||
required
|
id="username"
|
||||||
placeholder="username"
|
name="username"
|
||||||
type="username"
|
required
|
||||||
autoCapitalize="none"
|
placeholder="username"
|
||||||
autoComplete="username"
|
type="username"
|
||||||
autoCorrect="off"
|
autoCapitalize="none"
|
||||||
disabled={isLoading || isGitHubLoading}
|
autoComplete="username"
|
||||||
className="pl-9"
|
autoCorrect="off"
|
||||||
/>
|
disabled={isLoading || isGitHubLoading}
|
||||||
{errors?.username && <p className="px-1 text-xs text-red-600">{errors.username}</p>}
|
className="pl-9"
|
||||||
</div>
|
/>
|
||||||
)}
|
{errors?.username && (
|
||||||
<div className="grid gap-1 relative">
|
<p className="px-1 text-xs text-red-600">{errors.username}</p>
|
||||||
<MailIcon className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
|
)}
|
||||||
<Label className="sr-only" htmlFor="email">
|
</div>
|
||||||
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 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>
|
||||||
)}
|
|
||||||
<div className="sr-only">
|
|
||||||
{/* honeypot */}
|
|
||||||
<label htmlFor="name"></label>
|
|
||||||
<input id="name" type="text" name="name" tabIndex={-1} />
|
|
||||||
</div>
|
</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 && (
|
{authMethods.authProviders.length > 0 && (
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2 -mt-1">
|
||||||
{authProviders.map((provider) => (
|
{authMethods.authProviders.map((provider) => (
|
||||||
<button
|
<button
|
||||||
key={provider.name}
|
key={provider.name}
|
||||||
type="button"
|
type="button"
|
||||||
className={cn(buttonVariants({ variant: 'outline' }))}
|
className={cn(buttonVariants({ variant: 'outline' }), {
|
||||||
|
'justify-self-center': !authMethods.emailPassword,
|
||||||
|
'px-5': !authMethods.emailPassword,
|
||||||
|
})}
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
setIsOauthLoading(true)
|
setIsOauthLoading(true)
|
||||||
try {
|
try {
|
||||||
@@ -275,7 +277,7 @@ export function UserAuthForm({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!authProviders.length && (
|
{!authMethods.authProviders.length && (
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<button type="button" className={cn(buttonVariants({ variant: 'outline' }))}>
|
<button type="button" className={cn(buttonVariants({ variant: 'outline' }))}>
|
||||||
@@ -303,12 +305,15 @@ export function UserAuthForm({
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
)}
|
)}
|
||||||
<Link
|
|
||||||
href="/forgot-password"
|
{authMethods.emailPassword && (
|
||||||
className="text-sm mx-auto mt-2 hover:text-brand underline underline-offset-4 opacity-70 hover:opacity-100 transition-opacity"
|
<Link
|
||||||
>
|
href="/forgot-password"
|
||||||
Forgot password?
|
className="text-sm mx-auto hover:text-brand underline underline-offset-4 opacity-70 hover:opacity-100 transition-opacity"
|
||||||
</Link>
|
>
|
||||||
|
Forgot password?
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
</div>
|
</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 { Input } from '../ui/input'
|
||||||
import { Label } from '../ui/label'
|
import { Label } from '../ui/label'
|
||||||
import { useCallback, useState } from 'react'
|
import { useCallback, useState } from 'react'
|
||||||
@@ -70,7 +70,7 @@ export default function ForgotPassword() {
|
|||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<LoaderCircle className="mr-2 h-4 w-4 animate-spin" />
|
<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
|
Reset password
|
||||||
</button>
|
</button>
|
||||||
@@ -78,7 +78,7 @@ export default function ForgotPassword() {
|
|||||||
</form>
|
</form>
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger asChild>
|
<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
|
Command line instructions
|
||||||
</button>
|
</button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ import { pb } from '@/lib/stores'
|
|||||||
import { useStore } from '@nanostores/react'
|
import { useStore } from '@nanostores/react'
|
||||||
import ForgotPassword from './forgot-pass-form'
|
import ForgotPassword from './forgot-pass-form'
|
||||||
import { $router } from '../router'
|
import { $router } from '../router'
|
||||||
|
import { AuthMethodsList } from 'pocketbase'
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
const page = useStore($router)
|
const page = useStore($router)
|
||||||
const [isFirstRun, setFirstRun] = useState(false)
|
const [isFirstRun, setFirstRun] = useState(false)
|
||||||
|
const [authMethods, setAuthMethods] = useState<AuthMethodsList>()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
document.title = 'Login / Beszel'
|
document.title = 'Login / Beszel'
|
||||||
@@ -18,6 +20,14 @@ export default function () {
|
|||||||
})
|
})
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
pb.collection('users')
|
||||||
|
.listAuthMethods()
|
||||||
|
.then((methods) => {
|
||||||
|
setAuthMethods(methods)
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
const subtitle = useMemo(() => {
|
const subtitle = useMemo(() => {
|
||||||
if (isFirstRun) {
|
if (isFirstRun) {
|
||||||
return 'Please create an admin account'
|
return 'Please create an admin account'
|
||||||
@@ -28,9 +38,13 @@ export default function () {
|
|||||||
}
|
}
|
||||||
}, [isFirstRun, page])
|
}, [isFirstRun, page])
|
||||||
|
|
||||||
|
if (!authMethods) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen grid items-center py-12">
|
<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">
|
<div className="text-center">
|
||||||
<h1 className="mb-3">
|
<h1 className="mb-3">
|
||||||
<Logo className="h-7 fill-foreground mx-auto" />
|
<Logo className="h-7 fill-foreground mx-auto" />
|
||||||
@@ -41,7 +55,7 @@ export default function () {
|
|||||||
{page?.path === '/forgot-password' ? (
|
{page?.path === '/forgot-password' ? (
|
||||||
<ForgotPassword />
|
<ForgotPassword />
|
||||||
) : (
|
) : (
|
||||||
<UserAuthForm isFirstRun={isFirstRun} />
|
<UserAuthForm isFirstRun={isFirstRun} authMethods={authMethods} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user