user and password as authentication requirements (#87)

* user and password as authentication requirements

* updated README.md
This commit is contained in:
Marco
2023-09-23 11:41:01 +02:00
committed by GitHub
parent 19062c9f41
commit 8bbc8aa35e
5 changed files with 70 additions and 25 deletions

View File

@@ -107,6 +107,12 @@ Or with docker but building the container manually.
```sh ```sh
docker build -t yt-dlp-webui . docker build -t yt-dlp-webui .
docker run -d -p 3033:3033 -v <your dir>:/downloads yt-dlp-webui docker run -d -p 3033:3033 -v <your dir>:/downloads yt-dlp-webui
docker run -d -p 3033:3033 \
-v <your dir>:/downloads \
-v <your dir>:/config \ # optional
yt-dlp-webui
``` ```
If you opt to add RPC authentication... If you opt to add RPC authentication...
@@ -115,9 +121,11 @@ docker run -d \
-p 3033:3033 \ -p 3033:3033 \
-e JWT_SECRET randomsecret -e JWT_SECRET randomsecret
-v /path/to/downloads:/downloads \ -v /path/to/downloads:/downloads \
-v /path/for/config:/config \ # optional
marcobaobao/yt-dlp-webui \ marcobaobao/yt-dlp-webui \
--auth \ --auth \
--secret your_rpc_secret --user your_username \
--pass your_pass
``` ```
If you wish for limiting the download queue size... If you wish for limiting the download queue size...
@@ -163,8 +171,10 @@ Usage yt-dlp-webui:
Port where server will listen at (default 3033) Port where server will listen at (default 3033)
-qs int -qs int
Download queue size (default 8) Download queue size (default 8)
-secret string -user string
Secret required for auth Username required for auth
-pass string
Password required for auth
``` ```
### Config file ### Config file
@@ -181,7 +191,9 @@ downloaderPath: /usr/local/bin/yt-dlp
# Optional settings # Optional settings
require_auth: true require_auth: true
rpc_secret: my_random_secret username: my_username
password: my_random_secret
queue_size: 4 queue_size: 4
``` ```

View File

@@ -33,22 +33,32 @@ const Title = styled(Typography)({
}) })
export default function Login() { export default function Login() {
const [secret, setSecret] = useState('') const [username, setUsername] = useState('')
const [password, setPassword] = useState('')
const [formHasError, setFormHasError] = useState(false) const [formHasError, setFormHasError] = useState(false)
const url = useRecoilValue(serverURL) const url = useRecoilValue(serverURL)
const navigate = useNavigate() const navigate = useNavigate()
const navigateAndReload = () => {
navigate('/')
window.location.reload()
}
const login = async () => { const login = async () => {
const res = await fetch(`${url}/auth/login`, { const res = await fetch(`${url}/auth/login`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
}, },
body: JSON.stringify({ secret }) body: JSON.stringify({
username,
password,
})
}) })
res.ok ? navigate('/') : setFormHasError(true) res.ok ? navigateAndReload() : setFormHasError(true)
} }
return ( return (
@@ -62,17 +72,23 @@ export default function Login() {
Authentication token will expire after 30 days. Authentication token will expire after 30 days.
</Title> </Title>
<Title fontWeight={'500'} fontSize={16} color={'gray'}> <Title fontWeight={'500'} fontSize={16} color={'gray'}>
In order to enable RPC authentication append the --auth In order to enable RPC authentication append the --auth,
<br /> <br />
and --secret [secret] flags. --user [username] and --pass [password] flags.
</Title> </Title>
<TextField <TextField
id="outlined-password-input" label="Username"
label="RPC secret" type="text"
type="password" autoComplete="yt-dlp-webui-username"
autoComplete="current-password"
error={formHasError} error={formHasError}
onChange={e => setSecret(e.currentTarget.value)} onChange={e => setUsername(e.currentTarget.value)}
/>
<TextField
label="Password"
type="password"
autoComplete="yt-dlp-webui-password"
error={formHasError}
onChange={e => setPassword(e.currentTarget.value)}
/> />
<Button variant="contained" size="large" onClick={() => login()}> <Button variant="contained" size="large" onClick={() => login()}>
Submit Submit

16
main.go
View File

@@ -5,6 +5,7 @@ import (
"flag" "flag"
"io/fs" "io/fs"
"log" "log"
"os"
"runtime" "runtime"
"github.com/marcopeocchi/yt-dlp-web-ui/server" "github.com/marcopeocchi/yt-dlp-web-ui/server"
@@ -20,7 +21,11 @@ var (
downloaderPath string downloaderPath string
requireAuth bool requireAuth bool
rpcSecret string username string
password string
userFromEnv = os.Getenv("USERNAME")
passFromEnv = os.Getenv("PASSWORD")
//go:embed frontend/dist/index.html //go:embed frontend/dist/index.html
//go:embed frontend/dist/assets/* //go:embed frontend/dist/assets/*
@@ -28,6 +33,7 @@ var (
) )
func init() { func init() {
flag.IntVar(&port, "port", 3033, "Port where server will listen at") flag.IntVar(&port, "port", 3033, "Port where server will listen at")
flag.IntVar(&queueSize, "qs", runtime.NumCPU(), "Download queue size") flag.IntVar(&queueSize, "qs", runtime.NumCPU(), "Download queue size")
@@ -36,7 +42,8 @@ func init() {
flag.StringVar(&downloaderPath, "driver", "yt-dlp", "yt-dlp executable path") flag.StringVar(&downloaderPath, "driver", "yt-dlp", "yt-dlp executable path")
flag.BoolVar(&requireAuth, "auth", false, "Enable RPC authentication") flag.BoolVar(&requireAuth, "auth", false, "Enable RPC authentication")
flag.StringVar(&rpcSecret, "secret", "", "Secret required for auth") flag.StringVar(&username, "user", userFromEnv, "Username required for auth")
flag.StringVar(&password, "pass", passFromEnv, "Password required for auth")
flag.Parse() flag.Parse()
} }
@@ -56,10 +63,11 @@ func main() {
c.DownloaderPath(downloaderPath) c.DownloaderPath(downloaderPath)
c.RequireAuth(requireAuth) c.RequireAuth(requireAuth)
c.RPCSecret(rpcSecret) c.Username(username)
c.Password(password)
// if config file is found it will be merged with the current config struct // if config file is found it will be merged with the current config struct
if _, err := c.TryLoadFromFile(configFile); err != nil { if _, err := c.LoadFromFile(configFile); err != nil {
log.Println(cli.BgRed, "config", cli.Reset, "no config file found") log.Println(cli.BgRed, "config", cli.Reset, "no config file found")
} }

View File

@@ -14,7 +14,8 @@ type serverConfig struct {
DownloadPath string `yaml:"downloadPath"` DownloadPath string `yaml:"downloadPath"`
DownloaderPath string `yaml:"downloaderPath"` DownloaderPath string `yaml:"downloaderPath"`
RequireAuth bool `yaml:"require_auth"` RequireAuth bool `yaml:"require_auth"`
RPCSecret string `yaml:"rpc_secret"` Username string `yaml:"username"`
Password string `yaml:"password"`
QueueSize int `yaml:"queue_size"` QueueSize int `yaml:"queue_size"`
} }
@@ -22,7 +23,7 @@ type config struct {
cfg serverConfig cfg serverConfig
} }
func (c *config) TryLoadFromFile(filename string) (serverConfig, error) { func (c *config) LoadFromFile(filename string) (serverConfig, error) {
fd, err := os.Open(filename) fd, err := os.Open(filename)
if err != nil { if err != nil {
return serverConfig{}, err return serverConfig{}, err
@@ -55,8 +56,12 @@ func (c *config) RequireAuth(value bool) {
c.cfg.RequireAuth = value c.cfg.RequireAuth = value
} }
func (c *config) RPCSecret(secret string) { func (c *config) Username(username string) {
c.cfg.RPCSecret = secret c.cfg.Username = username
}
func (c *config) Password(password string) {
c.cfg.Password = password
} }
func (c *config) QueueSize(size int) { func (c *config) QueueSize(size int) {

View File

@@ -11,18 +11,21 @@ import (
) )
type LoginRequest struct { type LoginRequest struct {
Secret string `json:"secret"` Username string `json:"username"`
Password string `json:"password"`
} }
func Login(w http.ResponseWriter, r *http.Request) { func Login(w http.ResponseWriter, r *http.Request) {
req := new(LoginRequest) req := new(LoginRequest)
err := json.NewDecoder(r.Body).Decode(&req) err := json.NewDecoder(r.Body).Decode(req)
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
return return
} }
if config.Instance().GetConfig().RPCSecret != req.Secret { cfg := config.Instance().GetConfig()
if cfg.Username != req.Username || cfg.Password != req.Password {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
return return
} }
@@ -31,6 +34,7 @@ func Login(w http.ResponseWriter, r *http.Request) {
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"expiresAt": expiresAt, "expiresAt": expiresAt,
"username": req.Username,
}) })
tokenString, err := token.SignedString([]byte(os.Getenv("JWT_SECRET"))) tokenString, err := token.SignedString([]byte(os.Getenv("JWT_SECRET")))