mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 08:44:04 +01:00
Passwords: Enforce 72-character limit and improve bcrypt support #1987
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
@@ -20,7 +20,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
PHOTOPRISM_INIT: "https"
|
PHOTOPRISM_INIT: "https"
|
||||||
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
||||||
PHOTOPRISM_ADMIN_PASSWORD: "photoprism" # initial superadmin password (minimum 8 characters)
|
PHOTOPRISM_ADMIN_PASSWORD: "photoprism" # initial superadmin password (8-72 characters)
|
||||||
PHOTOPRISM_AUTH_MODE: "public" # authentication mode (public, password)
|
PHOTOPRISM_AUTH_MODE: "public" # authentication mode (public, password)
|
||||||
PHOTOPRISM_SITE_URL: "http://photoprism.me:2342/"
|
PHOTOPRISM_SITE_URL: "http://photoprism.me:2342/"
|
||||||
PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App"
|
PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App"
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ services:
|
|||||||
PHOTOPRISM_UID: ${UID:-1000} # user id, should match your host user id
|
PHOTOPRISM_UID: ${UID:-1000} # user id, should match your host user id
|
||||||
PHOTOPRISM_GID: ${GID:-1000} # group id
|
PHOTOPRISM_GID: ${GID:-1000} # group id
|
||||||
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
||||||
PHOTOPRISM_ADMIN_PASSWORD: "photoprism" # initial superadmin password (minimum 8 characters)
|
PHOTOPRISM_ADMIN_PASSWORD: "photoprism" # initial superadmin password (8-72 characters)
|
||||||
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
|
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
|
||||||
PHOTOPRISM_SITE_URL: "https://latest.localssl.dev/" # server URL in the format "http(s)://domain.name(:port)/(path)"
|
PHOTOPRISM_SITE_URL: "https://latest.localssl.dev/" # server URL in the format "http(s)://domain.name(:port)/(path)"
|
||||||
PHOTOPRISM_SITE_CAPTION: "Latest"
|
PHOTOPRISM_SITE_CAPTION: "Latest"
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ services:
|
|||||||
PHOTOPRISM_UID: ${UID:-1000} # user id, should match your host user id
|
PHOTOPRISM_UID: ${UID:-1000} # user id, should match your host user id
|
||||||
PHOTOPRISM_GID: ${GID:-1000} # group id
|
PHOTOPRISM_GID: ${GID:-1000} # group id
|
||||||
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
||||||
PHOTOPRISM_ADMIN_PASSWORD: "photoprism" # initial superadmin password (minimum 8 characters)
|
PHOTOPRISM_ADMIN_PASSWORD: "photoprism" # initial superadmin password (8-72 characters)
|
||||||
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
|
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
|
||||||
PHOTOPRISM_SITE_URL: "https://latest.localssl.dev/" # server URL in the format "http(s)://domain.name(:port)/(path)"
|
PHOTOPRISM_SITE_URL: "https://latest.localssl.dev/" # server URL in the format "http(s)://domain.name(:port)/(path)"
|
||||||
PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App"
|
PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App"
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
PHOTOPRISM_INIT: "https"
|
PHOTOPRISM_INIT: "https"
|
||||||
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
||||||
PHOTOPRISM_ADMIN_PASSWORD: "photoprism" # initial superadmin password (minimum 8 characters)
|
PHOTOPRISM_ADMIN_PASSWORD: "photoprism" # initial superadmin password (8-72 characters)
|
||||||
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
|
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
|
||||||
PHOTOPRISM_SITE_URL: "http://photoprism.me:2342/"
|
PHOTOPRISM_SITE_URL: "http://photoprism.me:2342/"
|
||||||
PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App"
|
PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App"
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ services:
|
|||||||
PHOTOPRISM_GID: ${GID:-1000} # group id
|
PHOTOPRISM_GID: ${GID:-1000} # group id
|
||||||
## Access Management
|
## Access Management
|
||||||
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
||||||
PHOTOPRISM_ADMIN_PASSWORD: "photoprism" # initial superadmin password (minimum 8 characters)
|
PHOTOPRISM_ADMIN_PASSWORD: "photoprism" # initial superadmin password (8-72 characters)
|
||||||
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
|
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
|
||||||
PHOTOPRISM_REGISTER_URI: "https://keycloak.localssl.dev/admin/"
|
PHOTOPRISM_REGISTER_URI: "https://keycloak.localssl.dev/admin/"
|
||||||
PHOTOPRISM_PASSWORD_RESET_URI: "https://keycloak.localssl.dev/realms/master/login-actions/reset-credentials"
|
PHOTOPRISM_PASSWORD_RESET_URI: "https://keycloak.localssl.dev/realms/master/login-actions/reset-credentials"
|
||||||
|
|||||||
@@ -1,59 +1,68 @@
|
|||||||
<template>
|
<template>
|
||||||
<v-dialog :value="show" lazy persistent max-width="500" class="modal-dialog p-account-password-dialog" @keydown.esc="cancel">
|
<v-dialog :value="show" lazy persistent max-width="500" class="modal-dialog p-account-password-dialog"
|
||||||
|
@keydown.esc="cancel">
|
||||||
<v-form ref="form" dense class="form-password" accept-charset="UTF-8">
|
<v-form ref="form" dense class="form-password" accept-charset="UTF-8">
|
||||||
<v-card raised elevation="24">
|
<v-card raised elevation="24">
|
||||||
<v-card-title primary-title class="pa-2">
|
<v-card-title primary-title class="pa-2">
|
||||||
<v-layout row wrap class="pa-2">
|
<v-layout row wrap class="pa-2">
|
||||||
<v-flex xs9 class="text-xs-left">
|
<v-flex xs9 class="text-xs-left">
|
||||||
<h3 class="headline pa-0"><translate>Change Password</translate></h3>
|
<h3 class="headline pa-0">
|
||||||
</v-flex>
|
<translate>Change Password</translate>
|
||||||
<v-flex xs3 class="text-xs-right">
|
</h3>
|
||||||
<v-icon size="28" color="primary">lock</v-icon>
|
</v-flex>
|
||||||
</v-flex>
|
<v-flex xs3 class="text-xs-right">
|
||||||
</v-layout>
|
<v-icon size="28" color="primary">lock</v-icon>
|
||||||
</v-card-title>
|
</v-flex>
|
||||||
<v-card-text class="py-0 px-2">
|
</v-layout>
|
||||||
<v-layout wrap align-top>
|
</v-card-title>
|
||||||
<v-flex v-if="oldRequired" xs12 class="px-2 pb-2 caption">
|
<v-card-text class="py-0 px-2">
|
||||||
<translate>Please note that changing your password will log you out on other devices and browsers.</translate>
|
<v-layout wrap align-top>
|
||||||
</v-flex>
|
<v-flex v-if="oldRequired" xs12 class="px-2 pb-2 caption">
|
||||||
<v-flex v-if="oldRequired" xs12 class="px-2 py-1">
|
<translate>Please note that changing your password will log you out on other devices and browsers.
|
||||||
<v-text-field
|
</translate>
|
||||||
|
</v-flex>
|
||||||
|
<v-flex v-if="oldRequired" xs12 class="px-2 py-1">
|
||||||
|
<v-text-field
|
||||||
v-model="oldPassword"
|
v-model="oldPassword"
|
||||||
hide-details required box flat
|
hide-details required box flat
|
||||||
type="password"
|
type="password"
|
||||||
:disabled="busy"
|
:disabled="busy"
|
||||||
|
:maxlength="maxLength"
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
autocapitalize="none"
|
autocapitalize="none"
|
||||||
:label="$gettext('Current Password')"
|
:label="$gettext('Current Password')"
|
||||||
class="input-current-password"
|
class="input-current-password"
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
|
|
||||||
<v-flex xs12 class="px-2 py-1">
|
<v-flex xs12 class="px-2 py-1">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="newPassword"
|
v-model="newPassword"
|
||||||
required counter persistent-hint box flat
|
required counter persistent-hint box flat
|
||||||
type="password"
|
type="password"
|
||||||
:disabled="busy"
|
:disabled="busy"
|
||||||
|
:minlength="minLength"
|
||||||
|
:maxlength="maxLength"
|
||||||
browser-autocomplete="new-password"
|
browser-autocomplete="new-password"
|
||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
autocapitalize="none"
|
autocapitalize="none"
|
||||||
:label="$gettext('New Password')"
|
:label="$gettext('New Password')"
|
||||||
class="input-new-password"
|
class="input-new-password"
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
:hint="$gettextInterpolate($gettext('Must have at least %{n} characters.'), {n: passwordLength})"
|
:hint="$gettextInterpolate($gettext('Must have at least %{n} characters.'), {n: minLength})"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
|
|
||||||
<v-flex xs12 class="px-2 py-1">
|
<v-flex xs12 class="px-2 py-1">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-model="confirmPassword"
|
v-model="confirmPassword"
|
||||||
required counter persistent-hint box flat
|
required counter persistent-hint box flat
|
||||||
type="password"
|
type="password"
|
||||||
:disabled="busy"
|
:disabled="busy"
|
||||||
|
:minlength="minLength"
|
||||||
|
:maxlength="maxLength"
|
||||||
browser-autocomplete="new-password"
|
browser-autocomplete="new-password"
|
||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
autocapitalize="none"
|
autocapitalize="none"
|
||||||
@@ -62,28 +71,28 @@
|
|||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
:hint="$gettext('Please confirm your new password.')"
|
:hint="$gettext('Please confirm your new password.')"
|
||||||
@keyup.enter.native="confirm"
|
@keyup.enter.native="confirm"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
</v-layout>
|
</v-layout>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
<v-card-actions class="pt-1 pb-2 px-2">
|
<v-card-actions class="pt-1 pb-2 px-2">
|
||||||
<v-layout row wrap class="pa-2">
|
<v-layout row wrap class="pa-2">
|
||||||
<v-flex xs12 text-xs-right>
|
<v-flex xs12 text-xs-right>
|
||||||
<v-btn depressed color="secondary-light"
|
<v-btn depressed color="secondary-light"
|
||||||
class="action-cancel ml-0"
|
class="action-cancel ml-0"
|
||||||
@click.stop="cancel">
|
@click.stop="cancel">
|
||||||
<translate>Cancel</translate>
|
<translate>Cancel</translate>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn depressed color="primary-button"
|
<v-btn depressed color="primary-button"
|
||||||
class="action-confirm white--text compact mr-0"
|
class="action-confirm white--text compact mr-0"
|
||||||
:disabled="disabled()"
|
:disabled="disabled()"
|
||||||
@click.stop="confirm">
|
@click.stop="confirm">
|
||||||
<translate>Save</translate>
|
<translate>Save</translate>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
</v-layout>
|
</v-layout>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-form>
|
</v-form>
|
||||||
</v-dialog>
|
</v-dialog>
|
||||||
</template>
|
</template>
|
||||||
@@ -107,7 +116,8 @@ export default {
|
|||||||
oldPassword: "",
|
oldPassword: "",
|
||||||
newPassword: "",
|
newPassword: "",
|
||||||
confirmPassword: "",
|
confirmPassword: "",
|
||||||
passwordLength: this.$config.get("passwordLength"),
|
minLength: this.$config.get("passwordLength"),
|
||||||
|
maxLength: 72,
|
||||||
rtl: this.$rtl,
|
rtl: this.$rtl,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@@ -123,13 +133,17 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
if(this.isPublic && !this.isDemo) {
|
if (this.isPublic && !this.isDemo) {
|
||||||
this.$emit('cancel');
|
this.$emit('cancel');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
disabled() {
|
disabled() {
|
||||||
return (this.isDemo || this.busy || this.oldPassword === "" && this.oldRequired || this.newPassword.length < this.passwordLength || (this.newPassword !== this.confirmPassword));
|
return (this.isDemo || this.busy
|
||||||
|
|| this.oldPassword === "" && this.oldRequired
|
||||||
|
|| this.newPassword.length < this.minLength
|
||||||
|
|| this.newPassword.length > this.maxLength
|
||||||
|
|| (this.newPassword !== this.confirmPassword));
|
||||||
},
|
},
|
||||||
confirm() {
|
confirm() {
|
||||||
this.busy = true;
|
this.busy = true;
|
||||||
|
|||||||
@@ -22,7 +22,13 @@ var PasswdCommand = cli.Command{
|
|||||||
Name: "passwd",
|
Name: "passwd",
|
||||||
Usage: "Changes the password of the user specified as argument",
|
Usage: "Changes the password of the user specified as argument",
|
||||||
ArgsUsage: "[username]",
|
ArgsUsage: "[username]",
|
||||||
Action: passwdAction,
|
Flags: []cli.Flag{
|
||||||
|
cli.BoolFlag{
|
||||||
|
Name: "show, s",
|
||||||
|
Usage: "show bcrypt password hash",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Action: passwdAction,
|
||||||
}
|
}
|
||||||
|
|
||||||
// passwdAction changes the password of the user specified as command argument.
|
// passwdAction changes the password of the user specified as command argument.
|
||||||
@@ -79,7 +85,12 @@ func passwdAction(ctx *cli.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("changed password for %s\n", clean.Log(m.Username()))
|
// Show bcrypt password hash?
|
||||||
|
if pw := entity.FindPassword(m.UserUID); ctx.Bool("show") && pw != nil {
|
||||||
|
log.Infof("password for %s successfully changed to %s\n", clean.Log(m.Username()), pw.Hash)
|
||||||
|
} else {
|
||||||
|
log.Infof("password for %s successfully changed\n", clean.Log(m.Username()))
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/photoprism/photoprism/internal/i18n"
|
"github.com/photoprism/photoprism/internal/i18n"
|
||||||
"github.com/photoprism/photoprism/internal/server/header"
|
"github.com/photoprism/photoprism/internal/server/header"
|
||||||
"github.com/photoprism/photoprism/internal/thumb"
|
"github.com/photoprism/photoprism/internal/thumb"
|
||||||
|
"github.com/photoprism/photoprism/pkg/txt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Flags configures the global command-line interface (CLI) parameters.
|
// Flags configures the global command-line interface (CLI) parameters.
|
||||||
@@ -37,7 +38,7 @@ var Flags = CliFlags{
|
|||||||
}}, {
|
}}, {
|
||||||
Flag: cli.StringFlag{
|
Flag: cli.StringFlag{
|
||||||
Name: "admin-password, pw",
|
Name: "admin-password, pw",
|
||||||
Usage: fmt.Sprintf("initial superadmin `PASSWORD` (minimum %d characters)", entity.PasswordLength),
|
Usage: fmt.Sprintf("initial superadmin `PASSWORD` (%d-%d characters)", entity.PasswordLength, txt.ClipPassword),
|
||||||
EnvVar: EnvVar("ADMIN_PASSWORD"),
|
EnvVar: EnvVar("ADMIN_PASSWORD"),
|
||||||
}}, {
|
}}, {
|
||||||
Flag: cli.Int64Flag{
|
Flag: cli.Int64Flag{
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
package entity
|
package entity
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
|
"github.com/photoprism/photoprism/pkg/txt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
PasswordCost = 14
|
||||||
)
|
)
|
||||||
|
|
||||||
// Password represents a password hash.
|
// Password represents a password hash.
|
||||||
@@ -20,15 +28,15 @@ func (Password) TableName() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewPassword creates a new password instance.
|
// NewPassword creates a new password instance.
|
||||||
func NewPassword(uid, password string) Password {
|
func NewPassword(uid, pw string) Password {
|
||||||
if uid == "" {
|
if uid == "" {
|
||||||
panic("auth: cannot set password without uid")
|
panic("auth: cannot set password without uid")
|
||||||
}
|
}
|
||||||
|
|
||||||
m := Password{UID: uid}
|
m := Password{UID: uid}
|
||||||
|
|
||||||
if password != "" {
|
if pw != "" {
|
||||||
if err := m.SetPassword(password); err != nil {
|
if err := m.SetPassword(pw); err != nil {
|
||||||
log.Errorf("auth: failed setting password for %s", uid)
|
log.Errorf("auth: failed setting password for %s", uid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -37,8 +45,23 @@ func NewPassword(uid, password string) Password {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SetPassword sets a new password stored as hash.
|
// SetPassword sets a new password stored as hash.
|
||||||
func (m *Password) SetPassword(password string) error {
|
func (m *Password) SetPassword(s string) error {
|
||||||
if bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14); err != nil {
|
s = clean.Password(s)
|
||||||
|
|
||||||
|
if l := len(s); l > txt.ClipPassword {
|
||||||
|
return fmt.Errorf("password is too long")
|
||||||
|
} else if l < 1 {
|
||||||
|
return fmt.Errorf("password is too short")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if string already is a bcrypt hash.
|
||||||
|
if cost, err := bcrypt.Cost([]byte(s)); err == nil && cost >= bcrypt.MinCost {
|
||||||
|
m.Hash = s
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate hash from plain text string.
|
||||||
|
if bytes, err := bcrypt.GenerateFromPassword([]byte(s), PasswordCost); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
m.Hash = string(bytes)
|
m.Hash = string(bytes)
|
||||||
@@ -46,19 +69,26 @@ func (m *Password) SetPassword(password string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is checks if the password is correct.
|
// IsValid checks if the password is correct.
|
||||||
func (m *Password) Is(s string) bool {
|
func (m *Password) IsValid(s string) bool {
|
||||||
return !m.IsWrong(s)
|
return !m.IsWrong(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsWrong checks if the specified password is incorrect.
|
// IsWrong checks if the specified password is incorrect.
|
||||||
func (m *Password) IsWrong(s string) bool {
|
func (m *Password) IsWrong(s string) bool {
|
||||||
if m.Hash == "" && s == "" {
|
if m.IsEmpty() {
|
||||||
return false
|
// No password set.
|
||||||
|
return true
|
||||||
|
} else if s = clean.Password(s); s == "" {
|
||||||
|
// No password provided.
|
||||||
|
return true
|
||||||
|
} else if err := bcrypt.CompareHashAndPassword([]byte(m.Hash), []byte(s)); err != nil {
|
||||||
|
// Wrong password.
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
err := bcrypt.CompareHashAndPassword([]byte(m.Hash), []byte(s))
|
// Ok.
|
||||||
return err != nil
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create inserts a new row to the database.
|
// Create inserts a new row to the database.
|
||||||
@@ -82,12 +112,21 @@ func FindPassword(uid string) *Password {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cost returns the hashing cost of the currently set password.
|
||||||
|
func (m *Password) Cost() (int, error) {
|
||||||
|
if m.IsEmpty() {
|
||||||
|
return 0, fmt.Errorf("password is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
return bcrypt.Cost([]byte(m.Hash))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsEmpty returns true if the password is not set.
|
||||||
|
func (m *Password) IsEmpty() bool {
|
||||||
|
return m.Hash == ""
|
||||||
|
}
|
||||||
|
|
||||||
// String returns the password hash.
|
// String returns the password hash.
|
||||||
func (m *Password) String() string {
|
func (m *Password) String() string {
|
||||||
return m.Hash
|
return m.Hash
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unknown returns true if the password is an empty string.
|
|
||||||
func (m *Password) Unknown() bool {
|
|
||||||
return m.Hash == ""
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,57 +8,85 @@ import (
|
|||||||
|
|
||||||
func TestNewPassword(t *testing.T) {
|
func TestNewPassword(t *testing.T) {
|
||||||
t.Run("success", func(t *testing.T) {
|
t.Run("success", func(t *testing.T) {
|
||||||
p := NewPassword("abc567", "passwd")
|
p := NewPassword("urrwaxd19ldtz68x", "passwd")
|
||||||
assert.Len(t, p.Hash, 60)
|
assert.Len(t, p.Hash, 60)
|
||||||
})
|
})
|
||||||
t.Run("empty password", func(t *testing.T) {
|
t.Run("empty password", func(t *testing.T) {
|
||||||
p := NewPassword("abc567", "")
|
p := NewPassword("urrwaxd19ldtz68x", "")
|
||||||
assert.Equal(t, "", p.Hash)
|
assert.Equal(t, "", p.Hash)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPassword_SetPassword(t *testing.T) {
|
func TestPassword_SetPassword(t *testing.T) {
|
||||||
t.Run("success", func(t *testing.T) {
|
t.Run("Text", func(t *testing.T) {
|
||||||
p := NewPassword("abc567", "passwd")
|
p := NewPassword("urrwaxd19ldtz68x", "passwd")
|
||||||
assert.Len(t, p.Hash, 60)
|
assert.Len(t, p.Hash, 60)
|
||||||
|
assert.True(t, p.IsValid("passwd"))
|
||||||
|
assert.False(t, p.IsValid("other"))
|
||||||
|
|
||||||
if err := p.SetPassword("abcd"); err != nil {
|
if err := p.SetPassword("abcd"); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Len(t, p.Hash, 60)
|
assert.Len(t, p.Hash, 60)
|
||||||
|
assert.True(t, p.IsValid("abcd"))
|
||||||
|
assert.False(t, p.IsValid("other"))
|
||||||
|
})
|
||||||
|
t.Run("Hash", func(t *testing.T) {
|
||||||
|
p := NewPassword("urrwaxd19ldtz68x", "$2a$14$qCcNjxupSJV1gjhgdYxz8e9l0e0fTZosX0s0qhMK54IkI9YOyWLt2")
|
||||||
|
assert.Len(t, p.Hash, 60)
|
||||||
|
assert.True(t, p.IsValid("photoprism"))
|
||||||
|
assert.False(t, p.IsValid("$2a$14$qCcNjxupSJV1gjhgdYxz8e9l0e0fTZosX0s0qhMK54IkI9YOyWLt2"))
|
||||||
|
assert.False(t, p.IsValid("other"))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPassword_Is(t *testing.T) {
|
func TestPassword_IsValid(t *testing.T) {
|
||||||
t.Run("Empty", func(t *testing.T) {
|
t.Run("EmptyHash", func(t *testing.T) {
|
||||||
p := Password{Hash: ""}
|
p := Password{Hash: ""}
|
||||||
assert.True(t, p.Is(""))
|
assert.True(t, p.IsEmpty())
|
||||||
|
assert.False(t, p.IsValid(""))
|
||||||
})
|
})
|
||||||
t.Run("Ok", func(t *testing.T) {
|
t.Run("EmptyPassword", func(t *testing.T) {
|
||||||
p := NewPassword("abc567", "")
|
p := NewPassword("urrwaxd19ldtz68x", "")
|
||||||
assert.True(t, p.Is(""))
|
assert.True(t, p.IsEmpty())
|
||||||
|
assert.False(t, p.IsValid(""))
|
||||||
})
|
})
|
||||||
t.Run("Wrong", func(t *testing.T) {
|
t.Run("ShortPassword", func(t *testing.T) {
|
||||||
p := NewPassword("abc567", "passwd")
|
p := NewPassword("urrwaxd19ldtz68x", "passwd")
|
||||||
assert.False(t, p.Is("$2a$14$p3HKuLvrTuePG/pjXLJQseUnSeAVeVO2cy4b0.34KXsLPK8lkI92G"))
|
assert.True(t, p.IsValid("passwd"))
|
||||||
|
assert.False(t, p.IsValid("$2a$14$p3HKuLvrTuePG/pjXLJQseUnSeAVeVO2cy4b0.34KXsLPK8lkI92G"))
|
||||||
|
})
|
||||||
|
t.Run("LongPassword", func(t *testing.T) {
|
||||||
|
p := NewPassword("urrwaxd19ldtz68x", "photoprism")
|
||||||
|
assert.True(t, p.IsValid("photoprism"))
|
||||||
|
assert.False(t, p.IsValid("$2a$14$p3HKuLvrTuePG/pjXLJQseUnSeAVeVO2cy4b0.34KXsLPK8lkI92G"))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPassword_IsWrong(t *testing.T) {
|
func TestPassword_IsWrong(t *testing.T) {
|
||||||
t.Run("Empty", func(t *testing.T) {
|
t.Run("EmptyHash", func(t *testing.T) {
|
||||||
p := Password{Hash: ""}
|
p := Password{Hash: ""}
|
||||||
assert.False(t, p.IsWrong(""))
|
assert.True(t, p.IsEmpty())
|
||||||
|
assert.True(t, p.IsWrong(""))
|
||||||
})
|
})
|
||||||
t.Run("Ok", func(t *testing.T) {
|
t.Run("EmptyPassword", func(t *testing.T) {
|
||||||
p := NewPassword("abc567", "")
|
p := NewPassword("urrwaxd19ldtz68x", "")
|
||||||
assert.False(t, p.IsWrong(""))
|
assert.True(t, p.IsEmpty())
|
||||||
|
assert.True(t, p.IsWrong(""))
|
||||||
})
|
})
|
||||||
t.Run("Wrong", func(t *testing.T) {
|
t.Run("ShortPassword", func(t *testing.T) {
|
||||||
p := NewPassword("abc567", "passwd")
|
p := NewPassword("urrwaxd19ldtz68x", "passwd")
|
||||||
assert.True(t, p.IsWrong("$2a$14$p3HKuLvrTuePG/pjXLJQseUnSeAVeVO2cy4b0.34KXsLPK8lkI92G"))
|
assert.True(t, p.IsWrong("$2a$14$p3HKuLvrTuePG/pjXLJQseUnSeAVeVO2cy4b0.34KXsLPK8lkI92G"))
|
||||||
|
assert.False(t, p.IsWrong("passwd"))
|
||||||
|
})
|
||||||
|
t.Run("LongPassword", func(t *testing.T) {
|
||||||
|
p := NewPassword("urrwaxd19ldtz68x", "photoprism")
|
||||||
|
assert.True(t, p.IsWrong("$2a$14$p3HKuLvrTuePG/pjXLJQseUnSeAVeVO2cy4b0.34KXsLPK8lkI92G"))
|
||||||
|
assert.False(t, p.IsWrong("photoprism"))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO fails on mariadb
|
|
||||||
func TestPassword_Create(t *testing.T) {
|
func TestPassword_Create(t *testing.T) {
|
||||||
t.Run("success", func(t *testing.T) {
|
t.Run("success", func(t *testing.T) {
|
||||||
p := Password{}
|
p := Password{}
|
||||||
@@ -72,26 +100,26 @@ func TestPassword_Create(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestFindPassword(t *testing.T) {
|
func TestFindPassword(t *testing.T) {
|
||||||
t.Run("not existing", func(t *testing.T) {
|
t.Run("NotFound", func(t *testing.T) {
|
||||||
r := FindPassword("xxx")
|
r := FindPassword("xxx")
|
||||||
assert.Nil(t, r)
|
assert.Nil(t, r)
|
||||||
})
|
})
|
||||||
t.Run("existing", func(t *testing.T) {
|
t.Run("Exists", func(t *testing.T) {
|
||||||
p := NewPassword("abc567", "passwd")
|
p := NewPassword("urrwaxd19ldtz68x", "passwd")
|
||||||
if err := p.Save(); err != nil {
|
if err := p.Save(); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
r := FindPassword("abc567")
|
r := FindPassword("urrwaxd19ldtz68x")
|
||||||
assert.NotEmpty(t, r)
|
assert.NotEmpty(t, r)
|
||||||
})
|
})
|
||||||
t.Run("alice", func(t *testing.T) {
|
t.Run("Alice", func(t *testing.T) {
|
||||||
if p := FindPassword("uqxetse3cy5eo9z2"); p == nil {
|
if p := FindPassword("uqxetse3cy5eo9z2"); p == nil {
|
||||||
t.Fatal("password not found")
|
t.Fatal("password not found")
|
||||||
} else {
|
} else {
|
||||||
assert.False(t, p.IsWrong("Alice123!"))
|
assert.False(t, p.IsWrong("Alice123!"))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
t.Run("bob", func(t *testing.T) {
|
t.Run("Bob", func(t *testing.T) {
|
||||||
if p := FindPassword("uqxc08w3d0ej2283"); p == nil {
|
if p := FindPassword("uqxc08w3d0ej2283"); p == nil {
|
||||||
t.Fatal("password not found")
|
t.Fatal("password not found")
|
||||||
} else {
|
} else {
|
||||||
@@ -100,20 +128,39 @@ func TestFindPassword(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPassword_Cost(t *testing.T) {
|
||||||
|
t.Run("Default", func(t *testing.T) {
|
||||||
|
p := NewPassword("urrwaxd19ldtz68x", "photoprism")
|
||||||
|
if cost, err := p.Cost(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, PasswordCost, cost)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("14", func(t *testing.T) {
|
||||||
|
p := NewPassword("urrwaxd19ldtz68x", "$2a$14$qCcNjxupSJV1gjhgdYxz8e9l0e0fTZosX0s0qhMK54IkI9YOyWLt2")
|
||||||
|
if cost, err := p.Cost(); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
assert.Equal(t, 14, cost)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestPassword_String(t *testing.T) {
|
func TestPassword_String(t *testing.T) {
|
||||||
t.Run("return string", func(t *testing.T) {
|
t.Run("return string", func(t *testing.T) {
|
||||||
p := NewPassword("abc567", "lkjhgtyu")
|
p := NewPassword("urrwaxd19ldtz68x", "lkjhgtyu")
|
||||||
assert.Len(t, p.String(), 60)
|
assert.Len(t, p.String(), 60)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPassword_Unknown(t *testing.T) {
|
func TestPassword_IsEmpty(t *testing.T) {
|
||||||
t.Run("false", func(t *testing.T) {
|
t.Run("false", func(t *testing.T) {
|
||||||
p := NewPassword("abc567", "lkjhgtyu")
|
p := NewPassword("urrwaxd19ldtz68x", "lkjhgtyu")
|
||||||
assert.False(t, p.Unknown())
|
assert.False(t, p.IsEmpty())
|
||||||
})
|
})
|
||||||
t.Run("true", func(t *testing.T) {
|
t.Run("true", func(t *testing.T) {
|
||||||
p := Password{}
|
p := Password{}
|
||||||
assert.True(t, p.Unknown())
|
assert.True(t, p.IsEmpty())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ const (
|
|||||||
ClipIP = 48
|
ClipIP = 48
|
||||||
ClipRealm = 64
|
ClipRealm = 64
|
||||||
ClipUserName = 64
|
ClipUserName = 64
|
||||||
|
ClipPassword = 72
|
||||||
ClipSlug = 80
|
ClipSlug = 80
|
||||||
ClipCategory = 100
|
ClipCategory = 100
|
||||||
ClipTokenName = 128
|
ClipTokenName = 128
|
||||||
@@ -27,7 +28,6 @@ const (
|
|||||||
ClipShortText = 1024
|
ClipShortText = 1024
|
||||||
ClipText = 2048
|
ClipText = 2048
|
||||||
ClipLongText = 4096
|
ClipLongText = 4096
|
||||||
ClipPassword = 4096
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Clip shortens a string to the given number of runes, and removes all leading and trailing white space.
|
// Clip shortens a string to the given number of runes, and removes all leading and trailing white space.
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ services:
|
|||||||
- "2342:2342" # HTTP port (host:container)
|
- "2342:2342" # HTTP port (host:container)
|
||||||
environment:
|
environment:
|
||||||
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
||||||
PHOTOPRISM_ADMIN_PASSWORD: "insecure" # initial superadmin password (minimum 8 characters)
|
PHOTOPRISM_ADMIN_PASSWORD: "insecure" # initial superadmin password (8-72 characters)
|
||||||
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
|
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
|
||||||
PHOTOPRISM_SITE_URL: "http://photoprism.me:2342/" # server URL in the format "http(s)://domain.name(:port)/(path)"
|
PHOTOPRISM_SITE_URL: "http://photoprism.me:2342/" # server URL in the format "http(s)://domain.name(:port)/(path)"
|
||||||
PHOTOPRISM_ORIGINALS_LIMIT: 5000 # file size limit for originals in MB (increase for high-res video)
|
PHOTOPRISM_ORIGINALS_LIMIT: 5000 # file size limit for originals in MB (increase for high-res video)
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ services:
|
|||||||
- "2342:2342" # HTTP port (host:container)
|
- "2342:2342" # HTTP port (host:container)
|
||||||
environment:
|
environment:
|
||||||
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
||||||
PHOTOPRISM_ADMIN_PASSWORD: "insecure" # initial superadmin password (minimum 8 characters)
|
PHOTOPRISM_ADMIN_PASSWORD: "insecure" # initial superadmin password (8-72 characters)
|
||||||
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
|
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
|
||||||
PHOTOPRISM_SITE_URL: "http://photoprism.me:2342/" # server URL in the format "http(s)://domain.name(:port)/(path)"
|
PHOTOPRISM_SITE_URL: "http://photoprism.me:2342/" # server URL in the format "http(s)://domain.name(:port)/(path)"
|
||||||
PHOTOPRISM_ORIGINALS_LIMIT: 5000 # file size limit for originals in MB (increase for high-res video)
|
PHOTOPRISM_ORIGINALS_LIMIT: 5000 # file size limit for originals in MB (increase for high-res video)
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ services:
|
|||||||
- "2342:2342" # HTTP port (host:container)
|
- "2342:2342" # HTTP port (host:container)
|
||||||
environment:
|
environment:
|
||||||
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
||||||
PHOTOPRISM_ADMIN_PASSWORD: "insecure" # initial superadmin password (minimum 8 characters)
|
PHOTOPRISM_ADMIN_PASSWORD: "insecure" # initial superadmin password (8-72 characters)
|
||||||
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
|
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
|
||||||
PHOTOPRISM_SITE_URL: "http://photoprism.me:2342/" # server URL in the format "http(s)://domain.name(:port)/(path)"
|
PHOTOPRISM_SITE_URL: "http://photoprism.me:2342/" # server URL in the format "http(s)://domain.name(:port)/(path)"
|
||||||
PHOTOPRISM_ORIGINALS_LIMIT: 5000 # file size limit for originals in MB (increase for high-res video)
|
PHOTOPRISM_ORIGINALS_LIMIT: 5000 # file size limit for originals in MB (increase for high-res video)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ services:
|
|||||||
- "2342:2342" # HTTP port (host:container)
|
- "2342:2342" # HTTP port (host:container)
|
||||||
environment:
|
environment:
|
||||||
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
||||||
PHOTOPRISM_ADMIN_PASSWORD: "insecure" # initial superadmin password (minimum 8 characters)
|
PHOTOPRISM_ADMIN_PASSWORD: "insecure" # initial superadmin password (8-72 characters)
|
||||||
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
|
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
|
||||||
PHOTOPRISM_SITE_URL: "http://photoprism.me:2342/" # server URL in the format "http(s)://domain.name(:port)/(path)"
|
PHOTOPRISM_SITE_URL: "http://photoprism.me:2342/" # server URL in the format "http(s)://domain.name(:port)/(path)"
|
||||||
PHOTOPRISM_ORIGINALS_LIMIT: 5000 # file size limit for originals in MB (increase for high-res video)
|
PHOTOPRISM_ORIGINALS_LIMIT: 5000 # file size limit for originals in MB (increase for high-res video)
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ services:
|
|||||||
- "2342:2342" # HTTP port (host:container)
|
- "2342:2342" # HTTP port (host:container)
|
||||||
environment:
|
environment:
|
||||||
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
||||||
PHOTOPRISM_ADMIN_PASSWORD: "insecure" # initial superadmin password (minimum 8 characters)
|
PHOTOPRISM_ADMIN_PASSWORD: "insecure" # initial superadmin password (8-72 characters)
|
||||||
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
|
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
|
||||||
PHOTOPRISM_SITE_URL: "http://photoprism.me:2342/" # server URL in the format "http(s)://domain.name(:port)/(path)"
|
PHOTOPRISM_SITE_URL: "http://photoprism.me:2342/" # server URL in the format "http(s)://domain.name(:port)/(path)"
|
||||||
PHOTOPRISM_ORIGINALS_LIMIT: 5000 # file size limit for originals in MB (increase for high-res video)
|
PHOTOPRISM_ORIGINALS_LIMIT: 5000 # file size limit for originals in MB (increase for high-res video)
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ services:
|
|||||||
- "2342:2342" # HTTP port (host:container)
|
- "2342:2342" # HTTP port (host:container)
|
||||||
environment:
|
environment:
|
||||||
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
||||||
PHOTOPRISM_ADMIN_PASSWORD: "insecure" # initial superadmin password (minimum 8 characters)
|
PHOTOPRISM_ADMIN_PASSWORD: "insecure" # initial superadmin password (8-72 characters)
|
||||||
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
|
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
|
||||||
PHOTOPRISM_SITE_URL: "http://photoprism.me:2342/" # server URL in the format "http(s)://domain.name(:port)/(path)"
|
PHOTOPRISM_SITE_URL: "http://photoprism.me:2342/" # server URL in the format "http(s)://domain.name(:port)/(path)"
|
||||||
PHOTOPRISM_ORIGINALS_LIMIT: 5000 # file size limit for originals in MB (increase for high-res video)
|
PHOTOPRISM_ORIGINALS_LIMIT: 5000 # file size limit for originals in MB (increase for high-res video)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ services:
|
|||||||
- "2342:2342" # HTTP port (host:container)
|
- "2342:2342" # HTTP port (host:container)
|
||||||
environment:
|
environment:
|
||||||
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
||||||
PHOTOPRISM_ADMIN_PASSWORD: "insecure" # initial superadmin password (minimum 8 characters)
|
PHOTOPRISM_ADMIN_PASSWORD: "insecure" # initial superadmin password (8-72 characters)
|
||||||
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
|
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
|
||||||
PHOTOPRISM_SITE_URL: "http://photoprism.me:2342/" # server URL in the format "http(s)://domain.name(:port)/(path)"
|
PHOTOPRISM_SITE_URL: "http://photoprism.me:2342/" # server URL in the format "http(s)://domain.name(:port)/(path)"
|
||||||
PHOTOPRISM_ORIGINALS_LIMIT: 5000 # file size limit for originals in MB (increase for high-res video)
|
PHOTOPRISM_ORIGINALS_LIMIT: 5000 # file size limit for originals in MB (increase for high-res video)
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ services:
|
|||||||
- "2342:2342" # HTTP port (host:container)
|
- "2342:2342" # HTTP port (host:container)
|
||||||
environment:
|
environment:
|
||||||
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
||||||
PHOTOPRISM_ADMIN_PASSWORD: "insecure" # initial superadmin password (minimum 8 characters)
|
PHOTOPRISM_ADMIN_PASSWORD: "insecure" # initial superadmin password (8-72 characters)
|
||||||
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
|
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
|
||||||
PHOTOPRISM_SITE_URL: "http://photoprism.me:2342/" # server URL in the format "http(s)://domain.name(:port)/(path)"
|
PHOTOPRISM_SITE_URL: "http://photoprism.me:2342/" # server URL in the format "http(s)://domain.name(:port)/(path)"
|
||||||
PHOTOPRISM_ORIGINALS_LIMIT: 5000 # file size limit for originals in MB (increase for high-res video)
|
PHOTOPRISM_ORIGINALS_LIMIT: 5000 # file size limit for originals in MB (increase for high-res video)
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ services:
|
|||||||
- "2342:2342" # HTTP port (host:container)
|
- "2342:2342" # HTTP port (host:container)
|
||||||
environment:
|
environment:
|
||||||
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
||||||
PHOTOPRISM_ADMIN_PASSWORD: "insecure" # initial superadmin password (minimum 8 characters)
|
PHOTOPRISM_ADMIN_PASSWORD: "insecure" # initial superadmin password (8-72 characters)
|
||||||
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
|
PHOTOPRISM_AUTH_MODE: "password" # authentication mode (public, password)
|
||||||
PHOTOPRISM_SITE_URL: "http://photoprism.me:2342/" # server URL in the format "http(s)://domain.name(:port)/(path)"
|
PHOTOPRISM_SITE_URL: "http://photoprism.me:2342/" # server URL in the format "http(s)://domain.name(:port)/(path)"
|
||||||
PHOTOPRISM_ORIGINALS_LIMIT: 5000 # file size limit for originals in MB (increase for high-res video)
|
PHOTOPRISM_ORIGINALS_LIMIT: 5000 # file size limit for originals in MB (increase for high-res video)
|
||||||
|
|||||||
Reference in New Issue
Block a user