Passwords: Enforce 72-character limit and improve bcrypt support #1987
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
parent
3575ccaec7
commit
163398b76c
20 changed files with 231 additions and 119 deletions
|
@ -20,7 +20,7 @@ services:
|
|||
environment:
|
||||
PHOTOPRISM_INIT: "https"
|
||||
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_SITE_URL: "http://photoprism.me:2342/"
|
||||
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_GID: ${GID:-1000} # group id
|
||||
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_SITE_URL: "https://latest.localssl.dev/" # server URL in the format "http(s)://domain.name(:port)/(path)"
|
||||
PHOTOPRISM_SITE_CAPTION: "Latest"
|
||||
|
|
|
@ -26,7 +26,7 @@ services:
|
|||
PHOTOPRISM_UID: ${UID:-1000} # user id, should match your host user id
|
||||
PHOTOPRISM_GID: ${GID:-1000} # group id
|
||||
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_SITE_URL: "https://latest.localssl.dev/" # server URL in the format "http(s)://domain.name(:port)/(path)"
|
||||
PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App"
|
||||
|
|
|
@ -28,7 +28,7 @@ services:
|
|||
environment:
|
||||
PHOTOPRISM_INIT: "https"
|
||||
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_SITE_URL: "http://photoprism.me:2342/"
|
||||
PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App"
|
||||
|
|
|
@ -42,7 +42,7 @@ services:
|
|||
PHOTOPRISM_GID: ${GID:-1000} # group id
|
||||
## Access Management
|
||||
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_REGISTER_URI: "https://keycloak.localssl.dev/admin/"
|
||||
PHOTOPRISM_PASSWORD_RESET_URI: "https://keycloak.localssl.dev/realms/master/login-actions/reset-credentials"
|
||||
|
|
|
@ -1,59 +1,68 @@
|
|||
<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-card raised elevation="24">
|
||||
<v-card-title primary-title class="pa-2">
|
||||
<v-layout row wrap class="pa-2">
|
||||
<v-flex xs9 class="text-xs-left">
|
||||
<h3 class="headline pa-0"><translate>Change Password</translate></h3>
|
||||
</v-flex>
|
||||
<v-flex xs3 class="text-xs-right">
|
||||
<v-icon size="28" color="primary">lock</v-icon>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-card-title>
|
||||
<v-card-text class="py-0 px-2">
|
||||
<v-layout wrap align-top>
|
||||
<v-flex v-if="oldRequired" xs12 class="px-2 pb-2 caption">
|
||||
<translate>Please note that changing your password will log you out on other devices and browsers.</translate>
|
||||
</v-flex>
|
||||
<v-flex v-if="oldRequired" xs12 class="px-2 py-1">
|
||||
<v-text-field
|
||||
<v-card raised elevation="24">
|
||||
<v-card-title primary-title class="pa-2">
|
||||
<v-layout row wrap class="pa-2">
|
||||
<v-flex xs9 class="text-xs-left">
|
||||
<h3 class="headline pa-0">
|
||||
<translate>Change Password</translate>
|
||||
</h3>
|
||||
</v-flex>
|
||||
<v-flex xs3 class="text-xs-right">
|
||||
<v-icon size="28" color="primary">lock</v-icon>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-card-title>
|
||||
<v-card-text class="py-0 px-2">
|
||||
<v-layout wrap align-top>
|
||||
<v-flex v-if="oldRequired" xs12 class="px-2 pb-2 caption">
|
||||
<translate>Please note that changing your password will log you out on other devices and browsers.
|
||||
</translate>
|
||||
</v-flex>
|
||||
<v-flex v-if="oldRequired" xs12 class="px-2 py-1">
|
||||
<v-text-field
|
||||
v-model="oldPassword"
|
||||
hide-details required box flat
|
||||
type="password"
|
||||
:disabled="busy"
|
||||
:maxlength="maxLength"
|
||||
browser-autocomplete="off"
|
||||
autocorrect="off"
|
||||
autocapitalize="none"
|
||||
:label="$gettext('Current Password')"
|
||||
class="input-current-password"
|
||||
color="secondary-dark"
|
||||
></v-text-field>
|
||||
</v-flex>
|
||||
></v-text-field>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 class="px-2 py-1">
|
||||
<v-text-field
|
||||
<v-flex xs12 class="px-2 py-1">
|
||||
<v-text-field
|
||||
v-model="newPassword"
|
||||
required counter persistent-hint box flat
|
||||
type="password"
|
||||
:disabled="busy"
|
||||
:minlength="minLength"
|
||||
:maxlength="maxLength"
|
||||
browser-autocomplete="new-password"
|
||||
autocorrect="off"
|
||||
autocapitalize="none"
|
||||
:label="$gettext('New Password')"
|
||||
class="input-new-password"
|
||||
color="secondary-dark"
|
||||
:hint="$gettextInterpolate($gettext('Must have at least %{n} characters.'), {n: passwordLength})"
|
||||
></v-text-field>
|
||||
</v-flex>
|
||||
:hint="$gettextInterpolate($gettext('Must have at least %{n} characters.'), {n: minLength})"
|
||||
></v-text-field>
|
||||
</v-flex>
|
||||
|
||||
<v-flex xs12 class="px-2 py-1">
|
||||
<v-text-field
|
||||
<v-flex xs12 class="px-2 py-1">
|
||||
<v-text-field
|
||||
v-model="confirmPassword"
|
||||
required counter persistent-hint box flat
|
||||
type="password"
|
||||
:disabled="busy"
|
||||
:minlength="minLength"
|
||||
:maxlength="maxLength"
|
||||
browser-autocomplete="new-password"
|
||||
autocorrect="off"
|
||||
autocapitalize="none"
|
||||
|
@ -62,28 +71,28 @@
|
|||
color="secondary-dark"
|
||||
:hint="$gettext('Please confirm your new password.')"
|
||||
@keyup.enter.native="confirm"
|
||||
></v-text-field>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-card-text>
|
||||
<v-card-actions class="pt-1 pb-2 px-2">
|
||||
<v-layout row wrap class="pa-2">
|
||||
<v-flex xs12 text-xs-right>
|
||||
<v-btn depressed color="secondary-light"
|
||||
class="action-cancel ml-0"
|
||||
@click.stop="cancel">
|
||||
<translate>Cancel</translate>
|
||||
</v-btn>
|
||||
<v-btn depressed color="primary-button"
|
||||
class="action-confirm white--text compact mr-0"
|
||||
:disabled="disabled()"
|
||||
@click.stop="confirm">
|
||||
<translate>Save</translate>
|
||||
</v-btn>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
></v-text-field>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-card-text>
|
||||
<v-card-actions class="pt-1 pb-2 px-2">
|
||||
<v-layout row wrap class="pa-2">
|
||||
<v-flex xs12 text-xs-right>
|
||||
<v-btn depressed color="secondary-light"
|
||||
class="action-cancel ml-0"
|
||||
@click.stop="cancel">
|
||||
<translate>Cancel</translate>
|
||||
</v-btn>
|
||||
<v-btn depressed color="primary-button"
|
||||
class="action-confirm white--text compact mr-0"
|
||||
:disabled="disabled()"
|
||||
@click.stop="confirm">
|
||||
<translate>Save</translate>
|
||||
</v-btn>
|
||||
</v-flex>
|
||||
</v-layout>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-form>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
@ -107,7 +116,8 @@ export default {
|
|||
oldPassword: "",
|
||||
newPassword: "",
|
||||
confirmPassword: "",
|
||||
passwordLength: this.$config.get("passwordLength"),
|
||||
minLength: this.$config.get("passwordLength"),
|
||||
maxLength: 72,
|
||||
rtl: this.$rtl,
|
||||
};
|
||||
},
|
||||
|
@ -123,13 +133,17 @@ export default {
|
|||
},
|
||||
},
|
||||
created() {
|
||||
if(this.isPublic && !this.isDemo) {
|
||||
if (this.isPublic && !this.isDemo) {
|
||||
this.$emit('cancel');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
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() {
|
||||
this.busy = true;
|
||||
|
|
|
@ -22,7 +22,13 @@ var PasswdCommand = cli.Command{
|
|||
Name: "passwd",
|
||||
Usage: "Changes the password of the user specified as argument",
|
||||
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.
|
||||
|
@ -79,7 +85,12 @@ func passwdAction(ctx *cli.Context) error {
|
|||
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
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/i18n"
|
||||
"github.com/photoprism/photoprism/internal/server/header"
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
// Flags configures the global command-line interface (CLI) parameters.
|
||||
|
@ -37,7 +38,7 @@ var Flags = CliFlags{
|
|||
}}, {
|
||||
Flag: cli.StringFlag{
|
||||
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"),
|
||||
}}, {
|
||||
Flag: cli.Int64Flag{
|
||||
|
|
|
@ -1,9 +1,17 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"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.
|
||||
|
@ -20,15 +28,15 @@ func (Password) TableName() string {
|
|||
}
|
||||
|
||||
// NewPassword creates a new password instance.
|
||||
func NewPassword(uid, password string) Password {
|
||||
func NewPassword(uid, pw string) Password {
|
||||
if uid == "" {
|
||||
panic("auth: cannot set password without uid")
|
||||
}
|
||||
|
||||
m := Password{UID: uid}
|
||||
|
||||
if password != "" {
|
||||
if err := m.SetPassword(password); err != nil {
|
||||
if pw != "" {
|
||||
if err := m.SetPassword(pw); err != nil {
|
||||
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.
|
||||
func (m *Password) SetPassword(password string) error {
|
||||
if bytes, err := bcrypt.GenerateFromPassword([]byte(password), 14); err != nil {
|
||||
func (m *Password) SetPassword(s string) error {
|
||||
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
|
||||
} else {
|
||||
m.Hash = string(bytes)
|
||||
|
@ -46,19 +69,26 @@ func (m *Password) SetPassword(password string) error {
|
|||
}
|
||||
}
|
||||
|
||||
// Is checks if the password is correct.
|
||||
func (m *Password) Is(s string) bool {
|
||||
// IsValid checks if the password is correct.
|
||||
func (m *Password) IsValid(s string) bool {
|
||||
return !m.IsWrong(s)
|
||||
}
|
||||
|
||||
// IsWrong checks if the specified password is incorrect.
|
||||
func (m *Password) IsWrong(s string) bool {
|
||||
if m.Hash == "" && s == "" {
|
||||
return false
|
||||
if m.IsEmpty() {
|
||||
// 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))
|
||||
return err != nil
|
||||
// Ok.
|
||||
return false
|
||||
}
|
||||
|
||||
// Create inserts a new row to the database.
|
||||
|
@ -82,12 +112,21 @@ func FindPassword(uid string) *Password {
|
|||
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.
|
||||
func (m *Password) String() string {
|
||||
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) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
p := NewPassword("abc567", "passwd")
|
||||
p := NewPassword("urrwaxd19ldtz68x", "passwd")
|
||||
assert.Len(t, p.Hash, 60)
|
||||
})
|
||||
t.Run("empty password", func(t *testing.T) {
|
||||
p := NewPassword("abc567", "")
|
||||
p := NewPassword("urrwaxd19ldtz68x", "")
|
||||
assert.Equal(t, "", p.Hash)
|
||||
})
|
||||
}
|
||||
|
||||
func TestPassword_SetPassword(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
p := NewPassword("abc567", "passwd")
|
||||
t.Run("Text", func(t *testing.T) {
|
||||
p := NewPassword("urrwaxd19ldtz68x", "passwd")
|
||||
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 {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
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) {
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
func TestPassword_IsValid(t *testing.T) {
|
||||
t.Run("EmptyHash", func(t *testing.T) {
|
||||
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) {
|
||||
p := NewPassword("abc567", "")
|
||||
assert.True(t, p.Is(""))
|
||||
t.Run("EmptyPassword", func(t *testing.T) {
|
||||
p := NewPassword("urrwaxd19ldtz68x", "")
|
||||
assert.True(t, p.IsEmpty())
|
||||
assert.False(t, p.IsValid(""))
|
||||
})
|
||||
t.Run("Wrong", func(t *testing.T) {
|
||||
p := NewPassword("abc567", "passwd")
|
||||
assert.False(t, p.Is("$2a$14$p3HKuLvrTuePG/pjXLJQseUnSeAVeVO2cy4b0.34KXsLPK8lkI92G"))
|
||||
t.Run("ShortPassword", func(t *testing.T) {
|
||||
p := NewPassword("urrwaxd19ldtz68x", "passwd")
|
||||
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) {
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
t.Run("EmptyHash", func(t *testing.T) {
|
||||
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) {
|
||||
p := NewPassword("abc567", "")
|
||||
assert.False(t, p.IsWrong(""))
|
||||
t.Run("EmptyPassword", func(t *testing.T) {
|
||||
p := NewPassword("urrwaxd19ldtz68x", "")
|
||||
assert.True(t, p.IsEmpty())
|
||||
assert.True(t, p.IsWrong(""))
|
||||
})
|
||||
t.Run("Wrong", func(t *testing.T) {
|
||||
p := NewPassword("abc567", "passwd")
|
||||
t.Run("ShortPassword", func(t *testing.T) {
|
||||
p := NewPassword("urrwaxd19ldtz68x", "passwd")
|
||||
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) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
p := Password{}
|
||||
|
@ -72,26 +100,26 @@ func TestPassword_Create(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")
|
||||
assert.Nil(t, r)
|
||||
})
|
||||
t.Run("existing", func(t *testing.T) {
|
||||
p := NewPassword("abc567", "passwd")
|
||||
t.Run("Exists", func(t *testing.T) {
|
||||
p := NewPassword("urrwaxd19ldtz68x", "passwd")
|
||||
if err := p.Save(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
r := FindPassword("abc567")
|
||||
r := FindPassword("urrwaxd19ldtz68x")
|
||||
assert.NotEmpty(t, r)
|
||||
})
|
||||
t.Run("alice", func(t *testing.T) {
|
||||
t.Run("Alice", func(t *testing.T) {
|
||||
if p := FindPassword("uqxetse3cy5eo9z2"); p == nil {
|
||||
t.Fatal("password not found")
|
||||
} else {
|
||||
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 {
|
||||
t.Fatal("password not found")
|
||||
} 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) {
|
||||
t.Run("return string", func(t *testing.T) {
|
||||
p := NewPassword("abc567", "lkjhgtyu")
|
||||
p := NewPassword("urrwaxd19ldtz68x", "lkjhgtyu")
|
||||
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) {
|
||||
p := NewPassword("abc567", "lkjhgtyu")
|
||||
assert.False(t, p.Unknown())
|
||||
p := NewPassword("urrwaxd19ldtz68x", "lkjhgtyu")
|
||||
assert.False(t, p.IsEmpty())
|
||||
})
|
||||
t.Run("true", func(t *testing.T) {
|
||||
p := Password{}
|
||||
assert.True(t, p.Unknown())
|
||||
assert.True(t, p.IsEmpty())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ const (
|
|||
ClipIP = 48
|
||||
ClipRealm = 64
|
||||
ClipUserName = 64
|
||||
ClipPassword = 72
|
||||
ClipSlug = 80
|
||||
ClipCategory = 100
|
||||
ClipTokenName = 128
|
||||
|
@ -27,7 +28,6 @@ const (
|
|||
ClipShortText = 1024
|
||||
ClipText = 2048
|
||||
ClipLongText = 4096
|
||||
ClipPassword = 4096
|
||||
)
|
||||
|
||||
// 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)
|
||||
environment:
|
||||
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_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)
|
||||
|
|
|
@ -47,7 +47,7 @@ services:
|
|||
- "2342:2342" # HTTP port (host:container)
|
||||
environment:
|
||||
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_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)
|
||||
|
|
|
@ -44,7 +44,7 @@ services:
|
|||
- "2342:2342" # HTTP port (host:container)
|
||||
environment:
|
||||
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_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)
|
||||
|
|
|
@ -40,7 +40,7 @@ services:
|
|||
- "2342:2342" # HTTP port (host:container)
|
||||
environment:
|
||||
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_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)
|
||||
|
|
|
@ -48,7 +48,7 @@ services:
|
|||
- "2342:2342" # HTTP port (host:container)
|
||||
environment:
|
||||
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_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)
|
||||
|
|
|
@ -42,7 +42,7 @@ services:
|
|||
- "2342:2342" # HTTP port (host:container)
|
||||
environment:
|
||||
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_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)
|
||||
|
|
|
@ -40,7 +40,7 @@ services:
|
|||
- "2342:2342" # HTTP port (host:container)
|
||||
environment:
|
||||
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_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)
|
||||
|
|
|
@ -46,7 +46,7 @@ services:
|
|||
- "2342:2342" # HTTP port (host:container)
|
||||
environment:
|
||||
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_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)
|
||||
|
|
|
@ -48,7 +48,7 @@ services:
|
|||
- "2342:2342" # HTTP port (host:container)
|
||||
environment:
|
||||
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_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)
|
||||
|
|
Loading…
Reference in a new issue