Auth: Add dummy LDAP service #98
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
parent
d8712b4636
commit
cc38922cbe
|
@ -19,8 +19,8 @@ services:
|
||||||
- "~/.cache/go-mod:/go/pkg/mod"
|
- "~/.cache/go-mod:/go/pkg/mod"
|
||||||
environment:
|
environment:
|
||||||
PHOTOPRISM_INIT: "https"
|
PHOTOPRISM_INIT: "https"
|
||||||
PHOTOPRISM_ADMIN_USER: "admin" # admin username
|
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
||||||
PHOTOPRISM_ADMIN_PASSWORD: "photoprism" # initial admin password (minimum 8 characters)
|
PHOTOPRISM_ADMIN_PASSWORD: "photoprism" # initial superadmin password (minimum 8 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"
|
||||||
|
|
|
@ -24,8 +24,8 @@ services:
|
||||||
PHOTOPRISM_INIT: "https"
|
PHOTOPRISM_INIT: "https"
|
||||||
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" # admin username
|
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
||||||
PHOTOPRISM_ADMIN_PASSWORD: "photoprism" # initial admin password (minimum 8 characters)
|
PHOTOPRISM_ADMIN_PASSWORD: "photoprism" # initial superadmin password (minimum 8 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"
|
||||||
|
|
|
@ -24,8 +24,8 @@ services:
|
||||||
environment:
|
environment:
|
||||||
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" # admin username
|
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
||||||
PHOTOPRISM_ADMIN_PASSWORD: "photoprism" # initial admin password (minimum 8 characters)
|
PHOTOPRISM_ADMIN_PASSWORD: "photoprism" # initial superadmin password (minimum 8 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"
|
||||||
|
|
|
@ -27,8 +27,8 @@ services:
|
||||||
shm_size: "2gb"
|
shm_size: "2gb"
|
||||||
environment:
|
environment:
|
||||||
PHOTOPRISM_INIT: "https"
|
PHOTOPRISM_INIT: "https"
|
||||||
PHOTOPRISM_ADMIN_USER: "admin" # admin username
|
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
||||||
PHOTOPRISM_ADMIN_PASSWORD: "photoprism" # initial admin password (minimum 8 characters)
|
PHOTOPRISM_ADMIN_PASSWORD: "photoprism" # initial superadmin password (minimum 8 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"
|
||||||
|
|
|
@ -24,8 +24,8 @@ services:
|
||||||
- "traefik:localssl.dev"
|
- "traefik:localssl.dev"
|
||||||
- "traefik:app.localssl.dev"
|
- "traefik:app.localssl.dev"
|
||||||
- "traefik:keycloak.localssl.dev"
|
- "traefik:keycloak.localssl.dev"
|
||||||
- "traefik:dummy-webdav.localssl.dev"
|
|
||||||
- "traefik:dummy-oidc.localssl.dev"
|
- "traefik:dummy-oidc.localssl.dev"
|
||||||
|
- "traefik:dummy-webdav.localssl.dev"
|
||||||
labels:
|
labels:
|
||||||
- "traefik.enable=true"
|
- "traefik.enable=true"
|
||||||
- "traefik.http.services.photoprism.loadbalancer.server.port=2342"
|
- "traefik.http.services.photoprism.loadbalancer.server.port=2342"
|
||||||
|
@ -39,9 +39,26 @@ services:
|
||||||
## Run as a non-root user after initialization (supported: 0, 33, 50-99, 500-600, and 900-1200):
|
## Run as a non-root user after initialization (supported: 0, 33, 50-99, 500-600, and 900-1200):
|
||||||
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" # admin username
|
## Access Management
|
||||||
PHOTOPRISM_ADMIN_PASSWORD: "photoprism" # initial admin password (minimum 8 characters)
|
PHOTOPRISM_ADMIN_USER: "admin" # superadmin username
|
||||||
|
PHOTOPRISM_ADMIN_PASSWORD: "photoprism" # initial superadmin password (minimum 8 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_PASSWORD_RESET_URI: "https://keycloak.localssl.dev/realms/master/login-actions/reset-credentials"
|
||||||
|
## LDAP Authentication (pre-configured for local tests):
|
||||||
|
PHOTOPRISM_LDAP_URI: "ldaps://dummy-ldap:1636"
|
||||||
|
PHOTOPRISM_LDAP_INSECURE: "true"
|
||||||
|
PHOTOPRISM_LDAP_ROLE: "user"
|
||||||
|
PHOTOPRISM_LDAP_WEBDAV: "true"
|
||||||
|
PHOTOPRISM_LDAP_BIND: "simple"
|
||||||
|
PHOTOPRISM_LDAP_BIND_DN: "cn"
|
||||||
|
PHOTOPRISM_LDAP_BASE_DN: "dc=localssl,dc=dev"
|
||||||
|
## OpenID Connect (pre-configured for local tests):
|
||||||
|
PHOTOPRISM_OIDC_URI: "https://keycloak.localssl.dev/auth/realms/master"
|
||||||
|
PHOTOPRISM_OIDC_INSECURE: "true"
|
||||||
|
PHOTOPRISM_OIDC_CLIENT: "photoprism-develop"
|
||||||
|
PHOTOPRISM_OIDC_SECRET: "9d8351a0-ca01-4556-9c37-85eb634869b9"
|
||||||
|
## Site Information
|
||||||
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_SITE_CAPTION: "AI-Powered Photos App"
|
PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App"
|
||||||
PHOTOPRISM_SITE_DESCRIPTION: "Tags and finds pictures without getting in your way!"
|
PHOTOPRISM_SITE_DESCRIPTION: "Tags and finds pictures without getting in your way!"
|
||||||
|
@ -83,10 +100,6 @@ services:
|
||||||
PHOTOPRISM_JPEG_SIZE: 7680 # size limit for converted image files in pixels (720-30000)
|
PHOTOPRISM_JPEG_SIZE: 7680 # size limit for converted image files in pixels (720-30000)
|
||||||
PHOTOPRISM_JPEG_QUALITY: 85 # a higher value increases the quality and file size of JPEG images and thumbnails (25-100)
|
PHOTOPRISM_JPEG_QUALITY: 85 # a higher value increases the quality and file size of JPEG images and thumbnails (25-100)
|
||||||
TF_CPP_MIN_LOG_LEVEL: 0 # show TensorFlow log messages for development
|
TF_CPP_MIN_LOG_LEVEL: 0 # show TensorFlow log messages for development
|
||||||
## OpenID Connect Provider (pre-configured for local Keycloak test server):
|
|
||||||
PHOTOPRISM_OIDC_ISSUER_URL: "https://keycloak.localssl.dev/auth/realms/master"
|
|
||||||
PHOTOPRISM_OIDC_CLIENT_ID: "photoprism-develop"
|
|
||||||
PHOTOPRISM_OIDC_CLIENT_SECRET: "9d8351a0-ca01-4556-9c37-85eb634869b9"
|
|
||||||
## Run/install on first startup (options: update https gpu tensorflow davfs clitools clean):
|
## Run/install on first startup (options: update https gpu tensorflow davfs clitools clean):
|
||||||
PHOTOPRISM_INIT: "https tensorflow"
|
PHOTOPRISM_INIT: "https tensorflow"
|
||||||
## Hardware Video Transcoding (optional):
|
## Hardware Video Transcoding (optional):
|
||||||
|
@ -151,6 +164,7 @@ services:
|
||||||
keycloak:
|
keycloak:
|
||||||
image: quay.io/keycloak/keycloak:19.0
|
image: quay.io/keycloak/keycloak:19.0
|
||||||
command: "start-dev" # development mode, do not use this in production!
|
command: "start-dev" # development mode, do not use this in production!
|
||||||
|
container_name: keycloak
|
||||||
links:
|
links:
|
||||||
- "traefik:localssl.dev"
|
- "traefik:localssl.dev"
|
||||||
- "traefik:app.localssl.dev"
|
- "traefik:app.localssl.dev"
|
||||||
|
@ -174,9 +188,32 @@ services:
|
||||||
KC_DB_USERNAME: "keycloak"
|
KC_DB_USERNAME: "keycloak"
|
||||||
KC_DB_PASSWORD: "keycloak"
|
KC_DB_PASSWORD: "keycloak"
|
||||||
|
|
||||||
|
## Dummy LDAP Server
|
||||||
|
dummy-ldap:
|
||||||
|
image: openidentityplatform/opendj:latest
|
||||||
|
container_name: dummy-ldap
|
||||||
|
expose:
|
||||||
|
- 1389
|
||||||
|
- 1636
|
||||||
|
- 4444
|
||||||
|
# ports:
|
||||||
|
# - "1389:1389"
|
||||||
|
# - "1636:1636"
|
||||||
|
# - "4444:4444"
|
||||||
|
user: "1001:1000"
|
||||||
|
environment:
|
||||||
|
OPENDJ_USER: 1001
|
||||||
|
PORT: 1389
|
||||||
|
LDAPS_PORT: 1636
|
||||||
|
BASE_DN: "dc=localssl,dc=dev"
|
||||||
|
ADD_BASE_ENTRY: "--addBaseEntry"
|
||||||
|
ROOT_USER_DN: "cn=user"
|
||||||
|
ROOT_PASSWORD: "photoprism"
|
||||||
|
|
||||||
## Dummy OpenID Connect Provider
|
## Dummy OpenID Connect Provider
|
||||||
dummy-oidc:
|
dummy-oidc:
|
||||||
image: photoprism/dummy-oidc:220405
|
image: photoprism/dummy-oidc:220405
|
||||||
|
container_name: dummy-oidc
|
||||||
labels:
|
labels:
|
||||||
- "traefik.enable=true"
|
- "traefik.enable=true"
|
||||||
- "traefik.http.services.dummy-oidc.loadbalancer.server.port=9998"
|
- "traefik.http.services.dummy-oidc.loadbalancer.server.port=9998"
|
||||||
|
@ -189,6 +226,7 @@ services:
|
||||||
## Dummy WebDAV Server
|
## Dummy WebDAV Server
|
||||||
dummy-webdav:
|
dummy-webdav:
|
||||||
image: photoprism/dummy-webdav:220405
|
image: photoprism/dummy-webdav:220405
|
||||||
|
container_name: dummy-webdav
|
||||||
environment:
|
environment:
|
||||||
WEBDAV_USERNAME: admin
|
WEBDAV_USERNAME: admin
|
||||||
WEBDAV_PASSWORD: photoprism
|
WEBDAV_PASSWORD: photoprism
|
||||||
|
|
|
@ -61,6 +61,7 @@ export default class Config {
|
||||||
this.themeName = "";
|
this.themeName = "";
|
||||||
this.baseUri = "";
|
this.baseUri = "";
|
||||||
this.staticUri = "/static";
|
this.staticUri = "/static";
|
||||||
|
this.loginUri = "/library/login";
|
||||||
this.apiUri = "/api/v1";
|
this.apiUri = "/api/v1";
|
||||||
this.contentUri = this.apiUri;
|
this.contentUri = this.apiUri;
|
||||||
this.values = {
|
this.values = {
|
||||||
|
@ -75,6 +76,7 @@ export default class Config {
|
||||||
} else {
|
} else {
|
||||||
this.baseUri = values.baseUri ? values.baseUri : "";
|
this.baseUri = values.baseUri ? values.baseUri : "";
|
||||||
this.staticUri = values.staticUri ? values.staticUri : this.baseUri + "/static";
|
this.staticUri = values.staticUri ? values.staticUri : this.baseUri + "/static";
|
||||||
|
this.loginUri = values.loginUri ? values.loginUri : this.baseUri + "/library/login";
|
||||||
this.apiUri = values.apiUri ? values.apiUri : this.baseUri + "/api/v1";
|
this.apiUri = values.apiUri ? values.apiUri : this.baseUri + "/api/v1";
|
||||||
this.contentUri = values.contentUri ? values.contentUri : this.apiUri;
|
this.contentUri = values.contentUri ? values.contentUri : this.apiUri;
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,6 +142,7 @@ export default class Session {
|
||||||
|
|
||||||
deleteId() {
|
deleteId() {
|
||||||
this.session_id = null;
|
this.session_id = null;
|
||||||
|
this.provider = "";
|
||||||
this.storage.removeItem("session_id");
|
this.storage.removeItem("session_id");
|
||||||
|
|
||||||
delete Api.defaults.headers.common[SessionHeader];
|
delete Api.defaults.headers.common[SessionHeader];
|
||||||
|
@ -157,6 +158,9 @@ export default class Session {
|
||||||
if (resp.data.id) {
|
if (resp.data.id) {
|
||||||
this.setId(resp.data.id);
|
this.setId(resp.data.id);
|
||||||
}
|
}
|
||||||
|
if (resp.data.provider) {
|
||||||
|
this.provider = resp.data.provider;
|
||||||
|
}
|
||||||
if (resp.data.config) {
|
if (resp.data.config) {
|
||||||
this.setConfig(resp.data.config);
|
this.setConfig(resp.data.config);
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,30 @@
|
||||||
max-width: 1264px;
|
max-width: 1264px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.width-50 {
|
||||||
|
min-width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.width-60 {
|
||||||
|
min-width: 60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.width-66 {
|
||||||
|
min-width: 66%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.width-70 {
|
||||||
|
min-width: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.width-80 {
|
||||||
|
min-width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.width-90 {
|
||||||
|
min-width: 90%;
|
||||||
|
}
|
||||||
|
|
||||||
/* Rounded Elements */
|
/* Rounded Elements */
|
||||||
|
|
||||||
.v-progress-linear,
|
.v-progress-linear,
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
:label="$gettext('New Password')"
|
:label="$gettext('New Password')"
|
||||||
class="input-new-password"
|
class="input-new-password"
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
:hint="$gettext('Must have at least 8 characters.')"
|
:hint="$gettextInterpolate($gettext('Must have at least %{n} characters.'), {n: passwordLength})"
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
</v-flex>
|
</v-flex>
|
||||||
|
|
||||||
|
@ -101,6 +101,7 @@ export default {
|
||||||
oldPassword: "",
|
oldPassword: "",
|
||||||
newPassword: "",
|
newPassword: "",
|
||||||
confirmPassword: "",
|
confirmPassword: "",
|
||||||
|
passwordLength: this.$config.get("passwordLength"),
|
||||||
rtl: this.$rtl,
|
rtl: this.$rtl,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -112,7 +113,7 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
disabled() {
|
disabled() {
|
||||||
return (this.isDemo || this.busy || this.oldPassword === "" || this.newPassword.length < 8 || (this.newPassword !== this.confirmPassword));
|
return (this.isDemo || this.busy || this.oldPassword === "" || this.newPassword.length < this.passwordLength || (this.newPassword !== this.confirmPassword));
|
||||||
},
|
},
|
||||||
confirm() {
|
confirm() {
|
||||||
this.busy = true;
|
this.busy = true;
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
:rules="[textRule]"
|
:rules="[textRule]"
|
||||||
hide-details box flat
|
hide-details box flat
|
||||||
:label="$gettext('Title')"
|
:label="$pgettext('Photo', 'Title')"
|
||||||
placeholder=""
|
placeholder=""
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
|
|
|
@ -45,13 +45,19 @@
|
||||||
></v-text-field>
|
></v-text-field>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<div class="action-buttons text-xs-center">
|
<div class="action-buttons text-xs-center">
|
||||||
<!-- a href="#" target="_blank" class="text-link px-2" :style="`color: ${colors.link}!important`"><translate>Forgot password?</translate></a -->
|
<v-btn v-if="registerUri" :color="colors.secondary" outline :block="$vuetify.breakpoint.xsOnly"
|
||||||
<v-btn :color="colors.primary" depressed :disabled="loginDisabled"
|
:style="`color: ${colors.link}!important`" class="action-register ra-6 px-3 py-2 opacity-80" @click.stop="register">
|
||||||
class="white--text action-confirm ra-6 px-3" @click.stop="login">
|
<translate>Create Account</translate>
|
||||||
|
</v-btn>
|
||||||
|
<v-btn :color="colors.primary" depressed :disabled="loginDisabled" :block="$vuetify.breakpoint.xsOnly"
|
||||||
|
class="white--text action-confirm ra-6 py-2 px-3" @click.stop="login">
|
||||||
<translate>Sign in</translate>
|
<translate>Sign in</translate>
|
||||||
<v-icon :right="!rtl" :left="rtl" dark>arrow_forward</v-icon>
|
<v-icon :right="!rtl" :left="rtl" dark>arrow_forward</v-icon>
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="passwordResetUri" class="text-xs-center opacity-80">
|
||||||
|
<a :href="passwordResetUri" class="text-link" :style="`color: ${colors.link}!important`"><translate>Forgot Password?</translate></a>
|
||||||
|
</div>
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
</v-form>
|
</v-form>
|
||||||
|
@ -103,7 +109,8 @@ export default {
|
||||||
return {
|
return {
|
||||||
colors: {
|
colors: {
|
||||||
accent: "#05dde1",
|
accent: "#05dde1",
|
||||||
primary: "#00adb0",
|
primary: "#00a6a9",
|
||||||
|
secondary: "#505050",
|
||||||
link: "#c8e3e7",
|
link: "#c8e3e7",
|
||||||
},
|
},
|
||||||
loading: false,
|
loading: false,
|
||||||
|
@ -115,6 +122,8 @@ export default {
|
||||||
siteDescription: this.$config.getSiteDescription(),
|
siteDescription: this.$config.getSiteDescription(),
|
||||||
nextUrl: this.$route.params.nextUrl ? this.$route.params.nextUrl : "/",
|
nextUrl: this.$route.params.nextUrl ? this.$route.params.nextUrl : "/",
|
||||||
wallpaperUri: this.$config.values.wallpaperUri,
|
wallpaperUri: this.$config.values.wallpaperUri,
|
||||||
|
registerUri: this.$config.values.registerUri,
|
||||||
|
passwordResetUri: this.$config.values.passwordResetUri,
|
||||||
rtl: this.$rtl,
|
rtl: this.$rtl,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -146,6 +155,9 @@ export default {
|
||||||
|
|
||||||
setTimeout(() => { window.location = route.href; }, 100);
|
setTimeout(() => { window.location = route.href; }, 100);
|
||||||
},
|
},
|
||||||
|
register() {
|
||||||
|
window.location = this.registerUri;
|
||||||
|
},
|
||||||
login() {
|
login() {
|
||||||
const username = this.username.trim();
|
const username = this.username.trim();
|
||||||
const password = this.password.trim();
|
const password = this.password.trim();
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
browser-autocomplete="off"
|
browser-autocomplete="off"
|
||||||
autocorrect="off"
|
autocorrect="off"
|
||||||
autocapitalize="none"
|
autocapitalize="none"
|
||||||
:label="$gettext('Title')"
|
:label="$pgettext('Account', 'Title')"
|
||||||
class="input-name-title"
|
class="input-name-title"
|
||||||
color="secondary-dark"
|
color="secondary-dark"
|
||||||
:rules="[v => validLength(v, 0, 32) || $gettext('Invalid')]"
|
:rules="[v => validLength(v, 0, 32) || $gettext('Invalid')]"
|
||||||
|
|
4
go.mod
4
go.mod
|
@ -97,6 +97,8 @@ require (
|
||||||
golang.org/x/time v0.2.0
|
golang.org/x/time v0.2.0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require github.com/go-ldap/ldap/v3 v3.4.4
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||||
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect
|
github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 // indirect
|
||||||
|
@ -105,6 +107,7 @@ require (
|
||||||
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
|
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
|
||||||
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
github.com/Azure/go-autorest/logger v0.2.1 // indirect
|
||||||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||||
|
github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
@ -112,6 +115,7 @@ require (
|
||||||
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
|
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
|
||||||
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect
|
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
|
github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect
|
||||||
github.com/go-errors/errors v1.4.2 // indirect
|
github.com/go-errors/errors v1.4.2 // indirect
|
||||||
github.com/go-playground/locales v0.14.0 // indirect
|
github.com/go-playground/locales v0.14.0 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.0 // indirect
|
github.com/go-playground/universal-translator v0.18.0 // indirect
|
||||||
|
|
8
go.sum
8
go.sum
|
@ -202,6 +202,8 @@ github.com/Azure/go-autorest/logger v0.2.1 h1:IG7i4p/mDa2Ce4TRyAO8IHnVhAVF3RFU+Z
|
||||||
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
|
github.com/Azure/go-autorest/logger v0.2.1/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8=
|
||||||
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
|
github.com/Azure/go-autorest/tracing v0.6.0 h1:TYi4+3m5t6K48TGI9AUdb+IzbnSxvnvUMfuitfgcfuo=
|
||||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||||
|
github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e h1:NeAW1fUYUEWhft7pkxDf6WoUvEZJ/uOKsvtpjLnn8MU=
|
||||||
|
github.com/Azure/go-ntlmssp v0.0.0-20220621081337-cb9428e4ac1e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
|
@ -382,6 +384,8 @@ github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR
|
||||||
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
|
||||||
github.com/go-acme/lego/v4 v4.9.0 h1:8Hjj44IqRS7cigshMyFQ+0pIZvwgkG/+9A0UnNh7G8A=
|
github.com/go-acme/lego/v4 v4.9.0 h1:8Hjj44IqRS7cigshMyFQ+0pIZvwgkG/+9A0UnNh7G8A=
|
||||||
github.com/go-acme/lego/v4 v4.9.0/go.mod h1:g3JRUyWS3L/VObpp4bCxzJftKyf/Wba8QrSSnoiqjg4=
|
github.com/go-acme/lego/v4 v4.9.0/go.mod h1:g3JRUyWS3L/VObpp4bCxzJftKyf/Wba8QrSSnoiqjg4=
|
||||||
|
github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A=
|
||||||
|
github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||||
github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs=
|
github.com/go-chi/chi/v5 v5.0.0/go.mod h1:BBug9lr0cqtdAhsu6R4AAdvufI0/XBzAQSsUqJpoZOs=
|
||||||
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
|
github.com/go-cmd/cmd v1.0.5/go.mod h1:y8q8qlK5wQibcw63djSl/ntiHUHXHGdCkPk0j4QeW4s=
|
||||||
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
|
||||||
|
@ -405,6 +409,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U=
|
github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U=
|
||||||
github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk=
|
github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk=
|
||||||
|
github.com/go-ldap/ldap/v3 v3.4.4 h1:qPjipEpt+qDa6SI/h1fzuGWoRUY+qqQ9sOZq67/PYUs=
|
||||||
|
github.com/go-ldap/ldap/v3 v3.4.4/go.mod h1:fe1MsuN5eJJ1FeLT/LEBVdWfNWKh459R7aXgXtJC+aI=
|
||||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
|
||||||
|
@ -918,6 +924,7 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||||
github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
|
@ -1022,6 +1029,7 @@ golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0
|
||||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
|
golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
|
||||||
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
|
||||||
|
|
|
@ -32,11 +32,12 @@ func CreateSession(router *gin.RouterGroup) {
|
||||||
if conf.Public() {
|
if conf.Public() {
|
||||||
sess := get.Session().Public()
|
sess := get.Session().Public()
|
||||||
data := gin.H{
|
data := gin.H{
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"id": sess.ID,
|
"id": sess.ID,
|
||||||
"user": sess.User(),
|
"provider": sess.AuthProvider,
|
||||||
"data": sess.Data(),
|
"user": sess.User(),
|
||||||
"config": conf.ClientPublic(),
|
"data": sess.Data(),
|
||||||
|
"config": conf.ClientPublic(),
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, data)
|
c.JSON(http.StatusOK, data)
|
||||||
return
|
return
|
||||||
|
@ -86,11 +87,12 @@ func CreateSession(router *gin.RouterGroup) {
|
||||||
|
|
||||||
// User information, session data, and client config values.
|
// User information, session data, and client config values.
|
||||||
data := gin.H{
|
data := gin.H{
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"id": sess.ID,
|
"id": sess.ID,
|
||||||
"user": sess.User(),
|
"provider": sess.AuthProvider,
|
||||||
"data": sess.Data(),
|
"user": sess.User(),
|
||||||
"config": clientConfig,
|
"data": sess.Data(),
|
||||||
|
"config": clientConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send JSON response.
|
// Send JSON response.
|
||||||
|
|
|
@ -55,11 +55,12 @@ func GetSession(router *gin.RouterGroup) {
|
||||||
|
|
||||||
// Send JSON response with user information, session data, and client config values.
|
// Send JSON response with user information, session data, and client config values.
|
||||||
data := gin.H{
|
data := gin.H{
|
||||||
"status": "ok",
|
"status": "ok",
|
||||||
"id": sess.ID,
|
"id": sess.ID,
|
||||||
"user": sess.User(),
|
"provider": sess.AuthProvider,
|
||||||
"data": sess.Data(),
|
"user": sess.User(),
|
||||||
"config": get.Config().ClientSession(sess),
|
"data": sess.Data(),
|
||||||
|
"config": get.Config().ClientSession(sess),
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, data)
|
c.JSON(http.StatusOK, data)
|
||||||
|
|
|
@ -58,8 +58,8 @@ func SaveSettings(router *gin.RouterGroup) {
|
||||||
|
|
||||||
var settings *customize.Settings
|
var settings *customize.Settings
|
||||||
|
|
||||||
if s.User().IsAdmin() {
|
// Only super admins can change global config defaults.
|
||||||
// Only admins may change the global config.
|
if s.User().IsSuperAdmin() {
|
||||||
settings = conf.Settings()
|
settings = conf.Settings()
|
||||||
|
|
||||||
if err := c.BindJSON(settings); err != nil {
|
if err := c.BindJSON(settings); err != nil {
|
||||||
|
|
|
@ -64,10 +64,10 @@ func usersAddAction(ctx *cli.Context) error {
|
||||||
frm.UserEmail = clean.Email(res)
|
frm.UserEmail = clean.Email(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
if interactive && len(ctx.String("password")) < entity.LenPasswordMin {
|
if interactive && len(ctx.String("password")) < entity.PasswordLength {
|
||||||
validate := func(input string) error {
|
validate := func(input string) error {
|
||||||
if len(input) < entity.LenPasswordMin {
|
if len(input) < entity.PasswordLength {
|
||||||
return fmt.Errorf("password must have at least %d characters", entity.LenPasswordMin)
|
return fmt.Errorf("password must have at least %d characters", entity.PasswordLength)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ var UsersListCommand = cli.Command{
|
||||||
// usersListAction displays existing user accounts.
|
// usersListAction displays existing user accounts.
|
||||||
func usersListAction(ctx *cli.Context) error {
|
func usersListAction(ctx *cli.Context) error {
|
||||||
return CallWithDependencies(ctx, func(conf *config.Config) error {
|
return CallWithDependencies(ctx, func(conf *config.Config) error {
|
||||||
cols := []string{"ID", "UID", "User Name", "Display Name", "Email", "Role", "Super Admin", "Web Login", "WebDAV", "Attributes", "Created At"}
|
cols := []string{"User", "Login", "Full Name", "Email", "Role", "Super Admin", "Web UI", "WebDAV", "Attributes", "Created At"}
|
||||||
|
|
||||||
// Fetch users from database.
|
// Fetch users from database.
|
||||||
users := query.RegisteredUsers()
|
users := query.RegisteredUsers()
|
||||||
|
@ -35,9 +35,8 @@ func usersListAction(ctx *cli.Context) error {
|
||||||
// Display report.
|
// Display report.
|
||||||
for i, user := range users {
|
for i, user := range users {
|
||||||
rows[i] = []string{
|
rows[i] = []string{
|
||||||
fmt.Sprintf("%d", user.ID),
|
|
||||||
user.UID(),
|
user.UID(),
|
||||||
user.Name(),
|
user.Login(),
|
||||||
user.FullName(),
|
user.FullName(),
|
||||||
user.Email(),
|
user.Email(),
|
||||||
user.AclRole().String(),
|
user.AclRole().String(),
|
||||||
|
|
|
@ -23,64 +23,68 @@ const (
|
||||||
|
|
||||||
// ClientConfig represents HTTP client / Web UI config options.
|
// ClientConfig represents HTTP client / Web UI config options.
|
||||||
type ClientConfig struct {
|
type ClientConfig struct {
|
||||||
Mode string `json:"mode"`
|
Mode string `json:"mode"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
About string `json:"about"`
|
About string `json:"about"`
|
||||||
Edition string `json:"edition"`
|
Edition string `json:"edition"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
Copyright string `json:"copyright"`
|
Copyright string `json:"copyright"`
|
||||||
Flags string `json:"flags"`
|
Flags string `json:"flags"`
|
||||||
BaseUri string `json:"baseUri"`
|
BaseUri string `json:"baseUri"`
|
||||||
StaticUri string `json:"staticUri"`
|
StaticUri string `json:"staticUri"`
|
||||||
CssUri string `json:"cssUri"`
|
CssUri string `json:"cssUri"`
|
||||||
JsUri string `json:"jsUri"`
|
JsUri string `json:"jsUri"`
|
||||||
ManifestUri string `json:"manifestUri"`
|
ManifestUri string `json:"manifestUri"`
|
||||||
ApiUri string `json:"apiUri"`
|
ApiUri string `json:"apiUri"`
|
||||||
ContentUri string `json:"contentUri"`
|
ContentUri string `json:"contentUri"`
|
||||||
WallpaperUri string `json:"wallpaperUri"`
|
WallpaperUri string `json:"wallpaperUri"`
|
||||||
SiteUrl string `json:"siteUrl"`
|
SiteUrl string `json:"siteUrl"`
|
||||||
SiteDomain string `json:"siteDomain"`
|
SiteDomain string `json:"siteDomain"`
|
||||||
SiteAuthor string `json:"siteAuthor"`
|
SiteAuthor string `json:"siteAuthor"`
|
||||||
SiteTitle string `json:"siteTitle"`
|
SiteTitle string `json:"siteTitle"`
|
||||||
SiteCaption string `json:"siteCaption"`
|
SiteCaption string `json:"siteCaption"`
|
||||||
SiteDescription string `json:"siteDescription"`
|
SiteDescription string `json:"siteDescription"`
|
||||||
SitePreview string `json:"sitePreview"`
|
SitePreview string `json:"sitePreview"`
|
||||||
LegalInfo string `json:"legalInfo"`
|
LegalInfo string `json:"legalInfo"`
|
||||||
LegalUrl string `json:"legalUrl"`
|
LegalUrl string `json:"legalUrl"`
|
||||||
AppName string `json:"appName"`
|
AppName string `json:"appName"`
|
||||||
AppMode string `json:"appMode"`
|
AppMode string `json:"appMode"`
|
||||||
AppIcon string `json:"appIcon"`
|
AppIcon string `json:"appIcon"`
|
||||||
Debug bool `json:"debug"`
|
Debug bool `json:"debug"`
|
||||||
Trace bool `json:"trace"`
|
Trace bool `json:"trace"`
|
||||||
Test bool `json:"test"`
|
Test bool `json:"test"`
|
||||||
Demo bool `json:"demo"`
|
Demo bool `json:"demo"`
|
||||||
Sponsor bool `json:"sponsor"`
|
Sponsor bool `json:"sponsor"`
|
||||||
ReadOnly bool `json:"readonly"`
|
ReadOnly bool `json:"readonly"`
|
||||||
UploadNSFW bool `json:"uploadNSFW"`
|
UploadNSFW bool `json:"uploadNSFW"`
|
||||||
Public bool `json:"public"`
|
Public bool `json:"public"`
|
||||||
AuthMode string `json:"authMode"`
|
AuthMode string `json:"authMode"`
|
||||||
Experimental bool `json:"experimental"`
|
LoginUri string `json:"loginUri"`
|
||||||
AlbumCategories []string `json:"albumCategories"`
|
RegisterUri string `json:"registerUri"`
|
||||||
Albums entity.Albums `json:"albums"`
|
PasswordLength int `json:"passwordLength"`
|
||||||
Cameras entity.Cameras `json:"cameras"`
|
PasswordResetUri string `json:"passwordResetUri"`
|
||||||
Lenses entity.Lenses `json:"lenses"`
|
Experimental bool `json:"experimental"`
|
||||||
Countries entity.Countries `json:"countries"`
|
AlbumCategories []string `json:"albumCategories"`
|
||||||
People entity.People `json:"people"`
|
Albums entity.Albums `json:"albums"`
|
||||||
Thumbs ThumbSizes `json:"thumbs"`
|
Cameras entity.Cameras `json:"cameras"`
|
||||||
MapKey string `json:"mapKey"`
|
Lenses entity.Lenses `json:"lenses"`
|
||||||
DownloadToken string `json:"downloadToken,omitempty"`
|
Countries entity.Countries `json:"countries"`
|
||||||
PreviewToken string `json:"previewToken,omitempty"`
|
People entity.People `json:"people"`
|
||||||
Disable ClientDisable `json:"disable"`
|
Thumbs ThumbSizes `json:"thumbs"`
|
||||||
Count ClientCounts `json:"count"`
|
MapKey string `json:"mapKey"`
|
||||||
Pos ClientPosition `json:"pos"`
|
DownloadToken string `json:"downloadToken,omitempty"`
|
||||||
Years Years `json:"years"`
|
PreviewToken string `json:"previewToken,omitempty"`
|
||||||
Colors []map[string]string `json:"colors"`
|
Disable ClientDisable `json:"disable"`
|
||||||
Categories CategoryLabels `json:"categories"`
|
Count ClientCounts `json:"count"`
|
||||||
Clip int `json:"clip"`
|
Pos ClientPosition `json:"pos"`
|
||||||
Server env.Resources `json:"server"`
|
Years Years `json:"years"`
|
||||||
Settings *customize.Settings `json:"settings,omitempty"`
|
Colors []map[string]string `json:"colors"`
|
||||||
ACL acl.Grants `json:"acl,omitempty"`
|
Categories CategoryLabels `json:"categories"`
|
||||||
Ext Values `json:"ext"`
|
Clip int `json:"clip"`
|
||||||
|
Server env.Resources `json:"server"`
|
||||||
|
Settings *customize.Settings `json:"settings,omitempty"`
|
||||||
|
ACL acl.Grants `json:"acl,omitempty"`
|
||||||
|
Ext Values `json:"ext"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ApplyACL updates the client config values based on the ACL and Role provided.
|
// ApplyACL updates the client config values based on the ACL and Role provided.
|
||||||
|
@ -225,54 +229,57 @@ func (c *Config) ClientPublic() ClientConfig {
|
||||||
Faces: true,
|
Faces: true,
|
||||||
Classification: true,
|
Classification: true,
|
||||||
},
|
},
|
||||||
Flags: strings.Join(c.Flags(), " "),
|
Flags: strings.Join(c.Flags(), " "),
|
||||||
Mode: string(ClientPublic),
|
Mode: string(ClientPublic),
|
||||||
Name: c.Name(),
|
Name: c.Name(),
|
||||||
About: c.Edition(),
|
About: c.Edition(),
|
||||||
Edition: c.Hub().Status,
|
Edition: c.Hub().Status,
|
||||||
BaseUri: c.BaseUri(""),
|
BaseUri: c.BaseUri(""),
|
||||||
StaticUri: c.StaticUri(),
|
StaticUri: c.StaticUri(),
|
||||||
CssUri: a.AppCssUri(),
|
CssUri: a.AppCssUri(),
|
||||||
JsUri: a.AppJsUri(),
|
JsUri: a.AppJsUri(),
|
||||||
ApiUri: c.ApiUri(),
|
ApiUri: c.ApiUri(),
|
||||||
ContentUri: c.ContentUri(),
|
ContentUri: c.ContentUri(),
|
||||||
SiteUrl: c.SiteUrl(),
|
SiteUrl: c.SiteUrl(),
|
||||||
SiteDomain: c.SiteDomain(),
|
SiteDomain: c.SiteDomain(),
|
||||||
SiteAuthor: c.SiteAuthor(),
|
SiteAuthor: c.SiteAuthor(),
|
||||||
SiteTitle: c.SiteTitle(),
|
SiteTitle: c.SiteTitle(),
|
||||||
SiteCaption: c.SiteCaption(),
|
SiteCaption: c.SiteCaption(),
|
||||||
SiteDescription: c.SiteDescription(),
|
SiteDescription: c.SiteDescription(),
|
||||||
SitePreview: c.SitePreview(),
|
SitePreview: c.SitePreview(),
|
||||||
LegalInfo: c.LegalInfo(),
|
LegalInfo: c.LegalInfo(),
|
||||||
LegalUrl: c.LegalUrl(),
|
LegalUrl: c.LegalUrl(),
|
||||||
AppName: c.AppName(),
|
AppName: c.AppName(),
|
||||||
AppMode: c.AppMode(),
|
AppMode: c.AppMode(),
|
||||||
AppIcon: c.AppIcon(),
|
AppIcon: c.AppIcon(),
|
||||||
WallpaperUri: c.WallpaperUri(),
|
WallpaperUri: c.WallpaperUri(),
|
||||||
Version: c.Version(),
|
Version: c.Version(),
|
||||||
Copyright: c.Copyright(),
|
Copyright: c.Copyright(),
|
||||||
Debug: c.Debug(),
|
Debug: c.Debug(),
|
||||||
Trace: c.Trace(),
|
Trace: c.Trace(),
|
||||||
Test: c.Test(),
|
Test: c.Test(),
|
||||||
Demo: c.Demo(),
|
Demo: c.Demo(),
|
||||||
Sponsor: c.Sponsor(),
|
Sponsor: c.Sponsor(),
|
||||||
ReadOnly: c.ReadOnly(),
|
ReadOnly: c.ReadOnly(),
|
||||||
Public: c.Public(),
|
Public: c.Public(),
|
||||||
AuthMode: c.AuthMode(),
|
AuthMode: c.AuthMode(),
|
||||||
Experimental: c.Experimental(),
|
LoginUri: c.LoginUri(),
|
||||||
Albums: entity.Albums{},
|
RegisterUri: c.RegisterUri(),
|
||||||
Cameras: entity.Cameras{},
|
PasswordResetUri: c.PasswordResetUri(),
|
||||||
Lenses: entity.Lenses{},
|
Experimental: c.Experimental(),
|
||||||
Countries: entity.Countries{},
|
Albums: entity.Albums{},
|
||||||
People: entity.People{},
|
Cameras: entity.Cameras{},
|
||||||
MapKey: "",
|
Lenses: entity.Lenses{},
|
||||||
Thumbs: Thumbs,
|
Countries: entity.Countries{},
|
||||||
Colors: colors.All.List(),
|
People: entity.People{},
|
||||||
ManifestUri: c.ClientManifestUri(),
|
MapKey: "",
|
||||||
Clip: txt.ClipDefault,
|
Thumbs: Thumbs,
|
||||||
PreviewToken: entity.TokenPublic,
|
Colors: colors.All.List(),
|
||||||
DownloadToken: entity.TokenPublic,
|
ManifestUri: c.ClientManifestUri(),
|
||||||
Ext: ClientExt(c, ClientPublic),
|
Clip: txt.ClipDefault,
|
||||||
|
PreviewToken: entity.TokenPublic,
|
||||||
|
DownloadToken: entity.TokenPublic,
|
||||||
|
Ext: ClientExt(c, ClientPublic),
|
||||||
}
|
}
|
||||||
|
|
||||||
return cfg
|
return cfg
|
||||||
|
@ -301,55 +308,58 @@ func (c *Config) ClientShare() ClientConfig {
|
||||||
Faces: true,
|
Faces: true,
|
||||||
Classification: true,
|
Classification: true,
|
||||||
},
|
},
|
||||||
Flags: strings.Join(c.Flags(), " "),
|
Flags: strings.Join(c.Flags(), " "),
|
||||||
Mode: string(ClientShare),
|
Mode: string(ClientShare),
|
||||||
Name: c.Name(),
|
Name: c.Name(),
|
||||||
About: c.Edition(),
|
About: c.Edition(),
|
||||||
Edition: c.Hub().Status,
|
Edition: c.Hub().Status,
|
||||||
BaseUri: c.BaseUri(""),
|
BaseUri: c.BaseUri(""),
|
||||||
StaticUri: c.StaticUri(),
|
StaticUri: c.StaticUri(),
|
||||||
CssUri: a.AppCssUri(),
|
CssUri: a.AppCssUri(),
|
||||||
JsUri: a.ShareJsUri(),
|
JsUri: a.ShareJsUri(),
|
||||||
ApiUri: c.ApiUri(),
|
ApiUri: c.ApiUri(),
|
||||||
ContentUri: c.ContentUri(),
|
ContentUri: c.ContentUri(),
|
||||||
SiteUrl: c.SiteUrl(),
|
SiteUrl: c.SiteUrl(),
|
||||||
SiteDomain: c.SiteDomain(),
|
SiteDomain: c.SiteDomain(),
|
||||||
SiteAuthor: c.SiteAuthor(),
|
SiteAuthor: c.SiteAuthor(),
|
||||||
SiteTitle: c.SiteTitle(),
|
SiteTitle: c.SiteTitle(),
|
||||||
SiteCaption: c.SiteCaption(),
|
SiteCaption: c.SiteCaption(),
|
||||||
SiteDescription: c.SiteDescription(),
|
SiteDescription: c.SiteDescription(),
|
||||||
SitePreview: c.SitePreview(),
|
SitePreview: c.SitePreview(),
|
||||||
LegalInfo: c.LegalInfo(),
|
LegalInfo: c.LegalInfo(),
|
||||||
LegalUrl: c.LegalUrl(),
|
LegalUrl: c.LegalUrl(),
|
||||||
AppName: c.AppName(),
|
AppName: c.AppName(),
|
||||||
AppMode: c.AppMode(),
|
AppMode: c.AppMode(),
|
||||||
AppIcon: c.AppIcon(),
|
AppIcon: c.AppIcon(),
|
||||||
WallpaperUri: c.WallpaperUri(),
|
WallpaperUri: c.WallpaperUri(),
|
||||||
Version: c.Version(),
|
Version: c.Version(),
|
||||||
Copyright: c.Copyright(),
|
Copyright: c.Copyright(),
|
||||||
Debug: c.Debug(),
|
Debug: c.Debug(),
|
||||||
Trace: c.Trace(),
|
Trace: c.Trace(),
|
||||||
Test: c.Test(),
|
Test: c.Test(),
|
||||||
Demo: c.Demo(),
|
Demo: c.Demo(),
|
||||||
Sponsor: c.Sponsor(),
|
Sponsor: c.Sponsor(),
|
||||||
ReadOnly: c.ReadOnly(),
|
ReadOnly: c.ReadOnly(),
|
||||||
UploadNSFW: c.UploadNSFW(),
|
UploadNSFW: c.UploadNSFW(),
|
||||||
Public: c.Public(),
|
Public: c.Public(),
|
||||||
AuthMode: c.AuthMode(),
|
AuthMode: c.AuthMode(),
|
||||||
Experimental: c.Experimental(),
|
LoginUri: c.LoginUri(),
|
||||||
Albums: entity.Albums{},
|
RegisterUri: c.RegisterUri(),
|
||||||
Cameras: entity.Cameras{},
|
PasswordResetUri: c.PasswordResetUri(),
|
||||||
Lenses: entity.Lenses{},
|
Experimental: c.Experimental(),
|
||||||
Countries: entity.Countries{},
|
Albums: entity.Albums{},
|
||||||
People: entity.People{},
|
Cameras: entity.Cameras{},
|
||||||
Colors: colors.All.List(),
|
Lenses: entity.Lenses{},
|
||||||
Thumbs: Thumbs,
|
Countries: entity.Countries{},
|
||||||
MapKey: c.Hub().MapKey(),
|
People: entity.People{},
|
||||||
DownloadToken: c.DownloadToken(),
|
Colors: colors.All.List(),
|
||||||
PreviewToken: c.PreviewToken(),
|
Thumbs: Thumbs,
|
||||||
ManifestUri: c.ClientManifestUri(),
|
MapKey: c.Hub().MapKey(),
|
||||||
Clip: txt.ClipDefault,
|
DownloadToken: c.DownloadToken(),
|
||||||
Ext: ClientExt(c, ClientShare),
|
PreviewToken: c.PreviewToken(),
|
||||||
|
ManifestUri: c.ClientManifestUri(),
|
||||||
|
Clip: txt.ClipDefault,
|
||||||
|
Ext: ClientExt(c, ClientShare),
|
||||||
}
|
}
|
||||||
|
|
||||||
return cfg
|
return cfg
|
||||||
|
@ -383,56 +393,60 @@ func (c *Config) ClientUser(withSettings bool) ClientConfig {
|
||||||
Faces: c.DisableFaces(),
|
Faces: c.DisableFaces(),
|
||||||
Classification: c.DisableClassification(),
|
Classification: c.DisableClassification(),
|
||||||
},
|
},
|
||||||
Flags: strings.Join(c.Flags(), " "),
|
Flags: strings.Join(c.Flags(), " "),
|
||||||
Mode: string(ClientUser),
|
Mode: string(ClientUser),
|
||||||
Name: c.Name(),
|
Name: c.Name(),
|
||||||
About: c.Edition(),
|
About: c.Edition(),
|
||||||
Edition: c.Hub().Status,
|
Edition: c.Hub().Status,
|
||||||
BaseUri: c.BaseUri(""),
|
BaseUri: c.BaseUri(""),
|
||||||
StaticUri: c.StaticUri(),
|
StaticUri: c.StaticUri(),
|
||||||
CssUri: a.AppCssUri(),
|
CssUri: a.AppCssUri(),
|
||||||
JsUri: a.AppJsUri(),
|
JsUri: a.AppJsUri(),
|
||||||
ApiUri: c.ApiUri(),
|
ApiUri: c.ApiUri(),
|
||||||
ContentUri: c.ContentUri(),
|
ContentUri: c.ContentUri(),
|
||||||
SiteUrl: c.SiteUrl(),
|
SiteUrl: c.SiteUrl(),
|
||||||
SiteDomain: c.SiteDomain(),
|
SiteDomain: c.SiteDomain(),
|
||||||
SiteAuthor: c.SiteAuthor(),
|
SiteAuthor: c.SiteAuthor(),
|
||||||
SiteTitle: c.SiteTitle(),
|
SiteTitle: c.SiteTitle(),
|
||||||
SiteCaption: c.SiteCaption(),
|
SiteCaption: c.SiteCaption(),
|
||||||
SiteDescription: c.SiteDescription(),
|
SiteDescription: c.SiteDescription(),
|
||||||
SitePreview: c.SitePreview(),
|
SitePreview: c.SitePreview(),
|
||||||
LegalInfo: c.LegalInfo(),
|
LegalInfo: c.LegalInfo(),
|
||||||
LegalUrl: c.LegalUrl(),
|
LegalUrl: c.LegalUrl(),
|
||||||
AppName: c.AppName(),
|
AppName: c.AppName(),
|
||||||
AppMode: c.AppMode(),
|
AppMode: c.AppMode(),
|
||||||
AppIcon: c.AppIcon(),
|
AppIcon: c.AppIcon(),
|
||||||
WallpaperUri: c.WallpaperUri(),
|
WallpaperUri: c.WallpaperUri(),
|
||||||
Version: c.Version(),
|
Version: c.Version(),
|
||||||
Copyright: c.Copyright(),
|
Copyright: c.Copyright(),
|
||||||
Debug: c.Debug(),
|
Debug: c.Debug(),
|
||||||
Trace: c.Trace(),
|
Trace: c.Trace(),
|
||||||
Test: c.Test(),
|
Test: c.Test(),
|
||||||
Demo: c.Demo(),
|
Demo: c.Demo(),
|
||||||
Sponsor: c.Sponsor(),
|
Sponsor: c.Sponsor(),
|
||||||
ReadOnly: c.ReadOnly(),
|
ReadOnly: c.ReadOnly(),
|
||||||
UploadNSFW: c.UploadNSFW(),
|
UploadNSFW: c.UploadNSFW(),
|
||||||
Public: c.Public(),
|
Public: c.Public(),
|
||||||
AuthMode: c.AuthMode(),
|
AuthMode: c.AuthMode(),
|
||||||
Experimental: c.Experimental(),
|
LoginUri: c.LoginUri(),
|
||||||
Albums: entity.Albums{},
|
RegisterUri: c.RegisterUri(),
|
||||||
Cameras: entity.Cameras{},
|
PasswordLength: c.PasswordLength(),
|
||||||
Lenses: entity.Lenses{},
|
PasswordResetUri: c.PasswordResetUri(),
|
||||||
Countries: entity.Countries{},
|
Experimental: c.Experimental(),
|
||||||
People: entity.People{},
|
Albums: entity.Albums{},
|
||||||
Colors: colors.All.List(),
|
Cameras: entity.Cameras{},
|
||||||
Thumbs: Thumbs,
|
Lenses: entity.Lenses{},
|
||||||
MapKey: c.Hub().MapKey(),
|
Countries: entity.Countries{},
|
||||||
DownloadToken: c.DownloadToken(),
|
People: entity.People{},
|
||||||
PreviewToken: c.PreviewToken(),
|
Colors: colors.All.List(),
|
||||||
ManifestUri: c.ClientManifestUri(),
|
Thumbs: Thumbs,
|
||||||
Clip: txt.ClipDefault,
|
MapKey: c.Hub().MapKey(),
|
||||||
Server: env.Info(),
|
DownloadToken: c.DownloadToken(),
|
||||||
Ext: ClientExt(c, ClientUser),
|
PreviewToken: c.PreviewToken(),
|
||||||
|
ManifestUri: c.ClientManifestUri(),
|
||||||
|
Clip: txt.ClipDefault,
|
||||||
|
Server: env.Info(),
|
||||||
|
Ext: ClientExt(c, ClientUser),
|
||||||
}
|
}
|
||||||
|
|
||||||
hidePrivate := c.Settings().Features.Private
|
hidePrivate := c.Settings().Features.Private
|
||||||
|
|
|
@ -17,6 +17,10 @@ func TestConfig_ClientConfig(t *testing.T) {
|
||||||
result := c.ClientPublic()
|
result := c.ClientPublic()
|
||||||
assert.IsType(t, ClientConfig{}, result)
|
assert.IsType(t, ClientConfig{}, result)
|
||||||
assert.Equal(t, AuthModePublic, result.AuthMode)
|
assert.Equal(t, AuthModePublic, result.AuthMode)
|
||||||
|
assert.Equal(t, "", result.LoginUri)
|
||||||
|
assert.Equal(t, "", result.RegisterUri)
|
||||||
|
assert.Equal(t, 0, result.PasswordLength)
|
||||||
|
assert.Equal(t, "", result.PasswordResetUri)
|
||||||
assert.Equal(t, true, result.Public)
|
assert.Equal(t, true, result.Public)
|
||||||
})
|
})
|
||||||
t.Run("TestErrorConfig", func(t *testing.T) {
|
t.Run("TestErrorConfig", func(t *testing.T) {
|
||||||
|
|
|
@ -166,6 +166,9 @@ func (c *Config) Propagate() {
|
||||||
places.UserAgent = c.UserAgent()
|
places.UserAgent = c.UserAgent()
|
||||||
entity.GeoApi = c.GeoApi()
|
entity.GeoApi = c.GeoApi()
|
||||||
|
|
||||||
|
// Set minimum password length.
|
||||||
|
entity.PasswordLength = c.PasswordLength()
|
||||||
|
|
||||||
// Set API preview and download default tokens.
|
// Set API preview and download default tokens.
|
||||||
entity.PreviewToken.Set(c.PreviewToken(), entity.TokenConfig)
|
entity.PreviewToken.Set(c.PreviewToken(), entity.TokenConfig)
|
||||||
entity.DownloadToken.Set(c.DownloadToken(), entity.TokenConfig)
|
entity.DownloadToken.Set(c.DownloadToken(), entity.TokenConfig)
|
||||||
|
@ -673,7 +676,7 @@ func (c *Config) AutoImport() time.Duration {
|
||||||
return time.Duration(c.options.AutoImport) * time.Second
|
return time.Duration(c.options.AutoImport) * time.Second
|
||||||
}
|
}
|
||||||
|
|
||||||
// GeoApi returns the preferred geocoding api (none or places).
|
// GeoApi returns the preferred geocoding api (places, or none).
|
||||||
func (c *Config) GeoApi() string {
|
func (c *Config) GeoApi() string {
|
||||||
if c.options.DisablePlaces {
|
if c.options.DisablePlaces {
|
||||||
return ""
|
return ""
|
||||||
|
|
|
@ -3,10 +3,9 @@ package config
|
||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/entity"
|
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
|
||||||
|
"github.com/photoprism/photoprism/internal/entity"
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
"github.com/photoprism/photoprism/pkg/rnd"
|
"github.com/photoprism/photoprism/pkg/rnd"
|
||||||
)
|
)
|
||||||
|
@ -85,6 +84,11 @@ func (c *Config) SetAuthMode(mode string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auth checks if authentication is required.
|
||||||
|
func (c *Config) Auth() bool {
|
||||||
|
return !c.Public()
|
||||||
|
}
|
||||||
|
|
||||||
// AuthMode returns the authentication mode.
|
// AuthMode returns the authentication mode.
|
||||||
func (c *Config) AuthMode() string {
|
func (c *Config) AuthMode() string {
|
||||||
if c.options.Public || c.options.Demo {
|
if c.options.Public || c.options.Demo {
|
||||||
|
@ -99,9 +103,46 @@ func (c *Config) AuthMode() string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth checks if authentication is required.
|
// LoginUri returns the user login URI.
|
||||||
func (c *Config) Auth() bool {
|
func (c *Config) LoginUri() string {
|
||||||
return !c.Public()
|
if c.Public() {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.options.LoginUri == "" {
|
||||||
|
return c.BaseUri("/library/login")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.options.LoginUri
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterUri returns the user registration URI.
|
||||||
|
func (c *Config) RegisterUri() string {
|
||||||
|
if c.Public() {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.options.RegisterUri
|
||||||
|
}
|
||||||
|
|
||||||
|
// PasswordLength returns the minimum password length in characters.
|
||||||
|
func (c *Config) PasswordLength() int {
|
||||||
|
if c.Public() {
|
||||||
|
return 0
|
||||||
|
} else if c.options.PasswordLength < 1 {
|
||||||
|
return 4
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.options.PasswordLength
|
||||||
|
}
|
||||||
|
|
||||||
|
// PasswordResetUri returns the password reset URI.
|
||||||
|
func (c *Config) PasswordResetUri() string {
|
||||||
|
if c.Public() {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.options.PasswordResetUri
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckPassword compares given password p with the admin password
|
// CheckPassword compares given password p with the admin password
|
||||||
|
|
|
@ -6,6 +6,18 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestAuth(t *testing.T) {
|
||||||
|
c := NewConfig(CliTestContext())
|
||||||
|
c.options.Public = true
|
||||||
|
c.options.Demo = false
|
||||||
|
assert.False(t, c.Auth())
|
||||||
|
c.options.Public = false
|
||||||
|
c.options.Demo = false
|
||||||
|
assert.True(t, c.Auth())
|
||||||
|
c.options.Demo = true
|
||||||
|
assert.False(t, c.Auth())
|
||||||
|
}
|
||||||
|
|
||||||
func TestAuthMode(t *testing.T) {
|
func TestAuthMode(t *testing.T) {
|
||||||
c := NewConfig(CliTestContext())
|
c := NewConfig(CliTestContext())
|
||||||
c.options.Public = true
|
c.options.Public = true
|
||||||
|
@ -34,16 +46,24 @@ func TestAuthMode(t *testing.T) {
|
||||||
c.options.Debug = false
|
c.options.Debug = false
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAuth(t *testing.T) {
|
func TestLoginUri(t *testing.T) {
|
||||||
c := NewConfig(CliTestContext())
|
c := NewConfig(CliTestContext())
|
||||||
c.options.Public = true
|
assert.Equal(t, "/library/login", c.LoginUri())
|
||||||
c.options.Demo = false
|
}
|
||||||
assert.False(t, c.Auth())
|
|
||||||
c.options.Public = false
|
func TestRegisterUri(t *testing.T) {
|
||||||
c.options.Demo = false
|
c := NewConfig(CliTestContext())
|
||||||
assert.True(t, c.Auth())
|
assert.Equal(t, "", c.RegisterUri())
|
||||||
c.options.Demo = true
|
}
|
||||||
assert.False(t, c.Auth())
|
|
||||||
|
func TestPasswordLength(t *testing.T) {
|
||||||
|
c := NewConfig(CliTestContext())
|
||||||
|
assert.Equal(t, 4, c.PasswordLength())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPasswordResetUri(t *testing.T) {
|
||||||
|
c := NewConfig(CliTestContext())
|
||||||
|
assert.Equal(t, "", c.PasswordResetUri())
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSessMaxAge(t *testing.T) {
|
func TestSessMaxAge(t *testing.T) {
|
||||||
|
|
|
@ -77,7 +77,7 @@ func (c *Config) HttpMode() string {
|
||||||
return c.options.HttpMode
|
return c.options.HttpMode
|
||||||
}
|
}
|
||||||
|
|
||||||
// HttpCompression returns the http compression method (none or gzip).
|
// HttpCompression returns the http compression method (gzip, or none).
|
||||||
func (c *Config) HttpCompression() string {
|
func (c *Config) HttpCompression() string {
|
||||||
return strings.ToLower(strings.TrimSpace(c.options.HttpCompression))
|
return strings.ToLower(strings.TrimSpace(c.options.HttpCompression))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/klauspost/cpuid/v2"
|
"github.com/klauspost/cpuid/v2"
|
||||||
"github.com/urfave/cli"
|
"github.com/urfave/cli"
|
||||||
|
|
||||||
|
"github.com/photoprism/photoprism/internal/entity"
|
||||||
"github.com/photoprism/photoprism/internal/face"
|
"github.com/photoprism/photoprism/internal/face"
|
||||||
"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"
|
||||||
|
@ -27,13 +30,13 @@ var Flags = CliFlags{
|
||||||
}}, {
|
}}, {
|
||||||
Flag: cli.StringFlag{
|
Flag: cli.StringFlag{
|
||||||
Name: "admin-user, login",
|
Name: "admin-user, login",
|
||||||
Usage: "admin login `USERNAME`",
|
Usage: "superadmin `USERNAME`",
|
||||||
EnvVar: "PHOTOPRISM_ADMIN_USER",
|
EnvVar: "PHOTOPRISM_ADMIN_USER",
|
||||||
Value: "admin",
|
Value: "admin",
|
||||||
}}, {
|
}}, {
|
||||||
Flag: cli.StringFlag{
|
Flag: cli.StringFlag{
|
||||||
Name: "admin-password, pw",
|
Name: "admin-password, pw",
|
||||||
Usage: "initial admin `PASSWORD`, must have at least 8 characters",
|
Usage: fmt.Sprintf("initial superadmin `PASSWORD` (minimum %d characters)", entity.PasswordLength),
|
||||||
EnvVar: "PHOTOPRISM_ADMIN_PASSWORD",
|
EnvVar: "PHOTOPRISM_ADMIN_PASSWORD",
|
||||||
}}, {
|
}}, {
|
||||||
Flag: cli.Int64Flag{
|
Flag: cli.Int64Flag{
|
||||||
|
@ -126,10 +129,7 @@ var Flags = CliFlags{
|
||||||
Value: DefaultResolutionLimit,
|
Value: DefaultResolutionLimit,
|
||||||
Usage: "maximum resolution of media files in `MEGAPIXELS` (1-900; -1 to disable)",
|
Usage: "maximum resolution of media files in `MEGAPIXELS` (1-900; -1 to disable)",
|
||||||
EnvVar: "PHOTOPRISM_RESOLUTION_LIMIT",
|
EnvVar: "PHOTOPRISM_RESOLUTION_LIMIT",
|
||||||
},
|
}, Tags: []string{EnvSponsor}}, {
|
||||||
Tags: []string{EnvSponsor},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Flag: cli.StringFlag{
|
Flag: cli.StringFlag{
|
||||||
Name: "storage-path, s",
|
Name: "storage-path, s",
|
||||||
Usage: "writable storage `PATH` for sidecar, cache, and database files",
|
Usage: "writable storage `PATH` for sidecar, cache, and database files",
|
||||||
|
@ -427,12 +427,12 @@ var Flags = CliFlags{
|
||||||
}}, {
|
}}, {
|
||||||
Flag: cli.StringFlag{
|
Flag: cli.StringFlag{
|
||||||
Name: "http-mode, mode",
|
Name: "http-mode, mode",
|
||||||
Usage: "Web server `MODE` (debug, release, or test)",
|
Usage: "Web server `MODE` (debug, release, test)",
|
||||||
EnvVar: "PHOTOPRISM_HTTP_MODE",
|
EnvVar: "PHOTOPRISM_HTTP_MODE",
|
||||||
}}, {
|
}}, {
|
||||||
Flag: cli.StringFlag{
|
Flag: cli.StringFlag{
|
||||||
Name: "http-compression, z",
|
Name: "http-compression, z",
|
||||||
Usage: "Web server compression `METHOD` (none or gzip)",
|
Usage: "Web server compression `METHOD` (gzip, none)",
|
||||||
EnvVar: "PHOTOPRISM_HTTP_COMPRESSION",
|
EnvVar: "PHOTOPRISM_HTTP_COMPRESSION",
|
||||||
}}, {
|
}}, {
|
||||||
Flag: cli.StringFlag{
|
Flag: cli.StringFlag{
|
||||||
|
|
|
@ -23,6 +23,10 @@ type Options struct {
|
||||||
Copyright string `json:"-"`
|
Copyright string `json:"-"`
|
||||||
PartnerID string `yaml:"-" json:"-" flag:"partner-id"`
|
PartnerID string `yaml:"-" json:"-" flag:"partner-id"`
|
||||||
AuthMode string `yaml:"AuthMode" json:"-" flag:"auth-mode"`
|
AuthMode string `yaml:"AuthMode" json:"-" flag:"auth-mode"`
|
||||||
|
LoginUri string `yaml:"LoginUri" json:"-" flag:"login-uri"`
|
||||||
|
RegisterUri string `yaml:"RegisterUri" json:"-" flag:"register-uri"`
|
||||||
|
PasswordLength int `yaml:"PasswordLength" json:"-" flag:"password-length"`
|
||||||
|
PasswordResetUri string `yaml:"PasswordResetUri" json:"-" flag:"password-reset-uri"`
|
||||||
Public bool `yaml:"Public" json:"-" flag:"public"`
|
Public bool `yaml:"Public" json:"-" flag:"public"`
|
||||||
AdminUser string `yaml:"AdminUser" json:"-" flag:"admin-user"`
|
AdminUser string `yaml:"AdminUser" json:"-" flag:"admin-user"`
|
||||||
AdminPassword string `yaml:"AdminPassword" json:"-" flag:"admin-password"`
|
AdminPassword string `yaml:"AdminPassword" json:"-" flag:"admin-password"`
|
||||||
|
|
|
@ -27,6 +27,10 @@ func (c *Config) Report() (rows [][]string, cols []string) {
|
||||||
{"public", fmt.Sprintf("%t", c.Public())},
|
{"public", fmt.Sprintf("%t", c.Public())},
|
||||||
{"session-maxage", fmt.Sprintf("%d", c.SessionMaxAge())},
|
{"session-maxage", fmt.Sprintf("%d", c.SessionMaxAge())},
|
||||||
{"session-timeout", fmt.Sprintf("%d", c.SessionTimeout())},
|
{"session-timeout", fmt.Sprintf("%d", c.SessionTimeout())},
|
||||||
|
{"login-uri", c.LoginUri()},
|
||||||
|
{"register-uri", c.RegisterUri()},
|
||||||
|
{"password-length", fmt.Sprintf("%d", c.PasswordLength())},
|
||||||
|
{"password-reset-uri", c.PasswordResetUri()},
|
||||||
|
|
||||||
// Logging.
|
// Logging.
|
||||||
{"log-level", c.LogLevel().String()},
|
{"log-level", c.LogLevel().String()},
|
||||||
|
|
|
@ -257,6 +257,26 @@ func (m *Session) SetUser(u *User) *Session {
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Login returns the login name and provider.
|
||||||
|
func (m *Session) Login() string {
|
||||||
|
if m.AuthProvider == "" {
|
||||||
|
return m.UserName
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf("%s@%s", m.UserName, m.AuthProvider)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetProvider updates the session's authentication provider.
|
||||||
|
func (m *Session) SetProvider(provider string) *Session {
|
||||||
|
if provider == "" {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
m.AuthProvider = provider
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
// ChangePassword changes the password of the current user.
|
// ChangePassword changes the password of the current user.
|
||||||
func (m *Session) ChangePassword(newPw string) (err error) {
|
func (m *Session) ChangePassword(newPw string) (err error) {
|
||||||
u := m.User()
|
u := m.User()
|
||||||
|
|
|
@ -4,8 +4,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/pkg/txt"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/event"
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
|
@ -13,61 +11,90 @@ import (
|
||||||
"github.com/photoprism/photoprism/internal/i18n"
|
"github.com/photoprism/photoprism/internal/i18n"
|
||||||
"github.com/photoprism/photoprism/internal/server/limiter"
|
"github.com/photoprism/photoprism/internal/server/limiter"
|
||||||
"github.com/photoprism/photoprism/pkg/clean"
|
"github.com/photoprism/photoprism/pkg/clean"
|
||||||
|
"github.com/photoprism/photoprism/pkg/txt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Auth checks if the credentials are valid and returns the user and authentication provider.
|
||||||
|
var Auth = func(f form.Login, m *Session, c *gin.Context) (user *User, provider string, err error) {
|
||||||
|
name := f.Name()
|
||||||
|
|
||||||
|
user = FindUserByName(name)
|
||||||
|
err = AuthPassword(user, f, m)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return user, ProviderNone, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return user, ProviderPassword, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthPassword checks if the username and password are valid and returns the user.
|
||||||
|
func AuthPassword(user *User, f form.Login, m *Session) (err error) {
|
||||||
|
name := f.Name()
|
||||||
|
|
||||||
|
// User found?
|
||||||
|
if user == nil {
|
||||||
|
message := "account not found"
|
||||||
|
limiter.Login.Reserve(m.IP())
|
||||||
|
event.AuditWarn([]string{m.IP(), "session %s", "login as %s", message}, m.RefID, clean.LogQuote(name))
|
||||||
|
event.LoginError(m.IP(), "api", name, m.UserAgent, message)
|
||||||
|
m.Status = http.StatusUnauthorized
|
||||||
|
return i18n.Error(i18n.ErrInvalidCredentials)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login allowed?
|
||||||
|
if !user.CanLogIn() {
|
||||||
|
message := "account disabled"
|
||||||
|
event.AuditWarn([]string{m.IP(), "session %s", "login as %s", message}, m.RefID, clean.LogQuote(name))
|
||||||
|
event.LoginError(m.IP(), "api", name, m.UserAgent, message)
|
||||||
|
m.Status = http.StatusUnauthorized
|
||||||
|
return i18n.Error(i18n.ErrInvalidCredentials)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Password valid?
|
||||||
|
if user.WrongPassword(f.Password) {
|
||||||
|
message := "incorrect password"
|
||||||
|
limiter.Login.Reserve(m.IP())
|
||||||
|
event.AuditErr([]string{m.IP(), "session %s", "login as %s", message}, m.RefID, clean.LogQuote(name))
|
||||||
|
event.LoginError(m.IP(), "api", name, m.UserAgent, message)
|
||||||
|
m.Status = http.StatusUnauthorized
|
||||||
|
return i18n.Error(i18n.ErrInvalidCredentials)
|
||||||
|
} else {
|
||||||
|
event.AuditInfo([]string{m.IP(), "session %s", "login as %s", "succeeded"}, m.RefID, clean.LogQuote(name))
|
||||||
|
event.LoginInfo(m.IP(), "api", name, m.UserAgent)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// LogIn performs authentication checks against the specified login form.
|
// LogIn performs authentication checks against the specified login form.
|
||||||
func (m *Session) LogIn(f form.Login, c *gin.Context) (err error) {
|
func (m *Session) LogIn(f form.Login, c *gin.Context) (err error) {
|
||||||
if c != nil {
|
if c != nil {
|
||||||
m.SetContext(c)
|
m.SetContext(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Username and password provided?
|
var user *User
|
||||||
|
var provider string
|
||||||
|
|
||||||
|
// Login credentials provided?
|
||||||
if f.HasCredentials() {
|
if f.HasCredentials() {
|
||||||
if m.IsRegistered() {
|
if m.IsRegistered() {
|
||||||
m.RegenerateID()
|
m.RegenerateID()
|
||||||
}
|
}
|
||||||
|
|
||||||
name := f.Name()
|
user, provider, err = Auth(f, m, c)
|
||||||
user := FindUserByName(name)
|
|
||||||
|
|
||||||
// User found?
|
if err != nil {
|
||||||
if user == nil {
|
return err
|
||||||
message := "account not found"
|
|
||||||
limiter.Login.Reserve(m.IP())
|
|
||||||
event.AuditWarn([]string{m.IP(), "session %s", "login as %s", message}, m.RefID, clean.LogQuote(name))
|
|
||||||
event.LoginError(m.IP(), "api", name, m.UserAgent, message)
|
|
||||||
m.Status = http.StatusUnauthorized
|
|
||||||
return i18n.Error(i18n.ErrInvalidCredentials)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Login allowed?
|
|
||||||
if !user.CanLogIn() {
|
|
||||||
message := "account disabled"
|
|
||||||
event.AuditWarn([]string{m.IP(), "session %s", "login as %s", message}, m.RefID, clean.LogQuote(name))
|
|
||||||
event.LoginError(m.IP(), "api", name, m.UserAgent, message)
|
|
||||||
m.Status = http.StatusUnauthorized
|
|
||||||
return i18n.Error(i18n.ErrInvalidCredentials)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Password valid?
|
|
||||||
if user.WrongPassword(f.Password) {
|
|
||||||
message := "incorrect password"
|
|
||||||
limiter.Login.Reserve(m.IP())
|
|
||||||
event.AuditErr([]string{m.IP(), "session %s", "login as %s", message}, m.RefID, clean.LogQuote(name))
|
|
||||||
event.LoginError(m.IP(), "api", name, m.UserAgent, message)
|
|
||||||
m.Status = http.StatusUnauthorized
|
|
||||||
return i18n.Error(i18n.ErrInvalidCredentials)
|
|
||||||
} else {
|
|
||||||
event.AuditInfo([]string{m.IP(), "session %s", "login as %s", "succeeded"}, m.RefID, clean.LogQuote(name))
|
|
||||||
event.LoginInfo(m.IP(), "api", name, m.UserAgent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m.SetUser(user)
|
m.SetUser(user)
|
||||||
|
m.SetProvider(provider)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Share token provided?
|
// Share token provided?
|
||||||
if f.HasToken() {
|
if f.HasToken() {
|
||||||
user := m.User()
|
user = m.User()
|
||||||
|
|
||||||
// Redeem token.
|
// Redeem token.
|
||||||
if user.IsRegistered() {
|
if user.IsRegistered() {
|
||||||
|
|
|
@ -8,9 +8,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ulule/deepcopier"
|
|
||||||
|
|
||||||
"github.com/jinzhu/gorm"
|
"github.com/jinzhu/gorm"
|
||||||
|
"github.com/ulule/deepcopier"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/acl"
|
"github.com/photoprism/photoprism/internal/acl"
|
||||||
"github.com/photoprism/photoprism/internal/event"
|
"github.com/photoprism/photoprism/internal/event"
|
||||||
|
@ -27,11 +26,11 @@ const (
|
||||||
OwnerUnknown = ""
|
OwnerUnknown = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
// LenNameMin specifies the minimum length of the username in characters.
|
// UsernameLength specifies the minimum length of the username in characters.
|
||||||
var LenNameMin = 3
|
var UsernameLength = 1
|
||||||
|
|
||||||
// LenPasswordMin specifies the minimum length of the password in characters.
|
// PasswordLength specifies the minimum length of the password in characters.
|
||||||
var LenPasswordMin = 4
|
var PasswordLength = 4
|
||||||
|
|
||||||
// Users represents a list of users.
|
// Users represents a list of users.
|
||||||
type Users []User
|
type Users []User
|
||||||
|
@ -110,6 +109,8 @@ func FindUser(find User) *User {
|
||||||
stmt = stmt.Where("user_name = ?", find.UserName)
|
stmt = stmt.Where("user_name = ?", find.UserName)
|
||||||
} else if find.UserEmail != "" {
|
} else if find.UserEmail != "" {
|
||||||
stmt = stmt.Where("user_email = ?", find.UserEmail)
|
stmt = stmt.Where("user_email = ?", find.UserEmail)
|
||||||
|
} else if find.AuthProvider != "" && find.AuthID != "" {
|
||||||
|
stmt = stmt.Where("auth_provider = ? AND auth_id = ?", find.AuthProvider, find.AuthID)
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -512,7 +513,16 @@ func (m *User) IsAdmin() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return m.IsRegistered() && (m.SuperAdmin || m.AclRole() == acl.RoleAdmin)
|
return m.IsSuperAdmin() || m.IsRegistered() && m.AclRole() == acl.RoleAdmin
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSuperAdmin checks if the user is a super admin.
|
||||||
|
func (m *User) IsSuperAdmin() bool {
|
||||||
|
if m == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return m.SuperAdmin
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsVisitor checks if the user is a sharing link visitor.
|
// IsVisitor checks if the user is a sharing link visitor.
|
||||||
|
@ -559,8 +569,8 @@ func (m *User) SetPassword(password string) error {
|
||||||
return fmt.Errorf("only registered users can change their password")
|
return fmt.Errorf("only registered users can change their password")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(password) < LenPasswordMin {
|
if len(password) < PasswordLength {
|
||||||
return fmt.Errorf("password must have at least %d characters", LenPasswordMin)
|
return fmt.Errorf("password must have at least %d characters", PasswordLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
pw := NewPassword(m.UserUID, password)
|
pw := NewPassword(m.UserUID, password)
|
||||||
|
@ -614,8 +624,8 @@ func (m *User) Validate() (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name too short?
|
// Name too short?
|
||||||
if len(m.Name()) < LenNameMin {
|
if len(m.Name()) < UsernameLength {
|
||||||
return fmt.Errorf("username must have at least %d characters", LenNameMin)
|
return fmt.Errorf("username must have at least %d characters", UsernameLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate user role.
|
// Validate user role.
|
||||||
|
@ -867,3 +877,12 @@ func (m *User) SetAvatar(thumb, thumbSrc string) error {
|
||||||
|
|
||||||
return m.Updates(Values{"Thumb": m.Thumb, "ThumbSrc": m.ThumbSrc})
|
return m.Updates(Values{"Thumb": m.Thumb, "ThumbSrc": m.ThumbSrc})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Login returns the login name and provider.
|
||||||
|
func (m *User) Login() string {
|
||||||
|
if m.AuthProvider == "" {
|
||||||
|
return m.UserName
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf("%s@%s", m.UserName, m.AuthProvider)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -13,8 +13,8 @@ import (
|
||||||
func AddUser(frm form.User) error {
|
func AddUser(frm form.User) error {
|
||||||
user := NewUser().SetFormValues(frm)
|
user := NewUser().SetFormValues(frm)
|
||||||
|
|
||||||
if len(frm.Password) < LenPasswordMin {
|
if len(frm.Password) < PasswordLength {
|
||||||
return fmt.Errorf("password must have at least %d characters", LenPasswordMin)
|
return fmt.Errorf("password must have at least %d characters", PasswordLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := user.Validate(); err != nil {
|
if err := user.Validate(); err != nil {
|
||||||
|
|
|
@ -333,6 +333,7 @@ func TestFindUserByUID(t *testing.T) {
|
||||||
assert.Equal(t, "alice@example.com", m.UserEmail)
|
assert.Equal(t, "alice@example.com", m.UserEmail)
|
||||||
assert.True(t, m.SuperAdmin)
|
assert.True(t, m.SuperAdmin)
|
||||||
assert.True(t, m.IsAdmin())
|
assert.True(t, m.IsAdmin())
|
||||||
|
assert.True(t, m.IsSuperAdmin())
|
||||||
assert.False(t, m.IsVisitor())
|
assert.False(t, m.IsVisitor())
|
||||||
assert.True(t, m.CanLogin)
|
assert.True(t, m.CanLogin)
|
||||||
assert.NotEmpty(t, m.CreatedAt)
|
assert.NotEmpty(t, m.CreatedAt)
|
||||||
|
@ -352,6 +353,7 @@ func TestFindUserByUID(t *testing.T) {
|
||||||
assert.Equal(t, "bob@example.com", m.UserEmail)
|
assert.Equal(t, "bob@example.com", m.UserEmail)
|
||||||
assert.False(t, m.SuperAdmin)
|
assert.False(t, m.SuperAdmin)
|
||||||
assert.True(t, m.IsAdmin())
|
assert.True(t, m.IsAdmin())
|
||||||
|
assert.False(t, m.IsSuperAdmin())
|
||||||
assert.False(t, m.IsVisitor())
|
assert.False(t, m.IsVisitor())
|
||||||
assert.True(t, m.CanLogin)
|
assert.True(t, m.CanLogin)
|
||||||
assert.NotEmpty(t, m.CreatedAt)
|
assert.NotEmpty(t, m.CreatedAt)
|
||||||
|
@ -537,15 +539,6 @@ func TestUser_Validate(t *testing.T) {
|
||||||
|
|
||||||
assert.Error(t, u.Validate())
|
assert.Error(t, u.Validate())
|
||||||
})
|
})
|
||||||
t.Run("NameTooShort", func(t *testing.T) {
|
|
||||||
u := &User{
|
|
||||||
UserName: "va",
|
|
||||||
DisplayName: "Validate",
|
|
||||||
UserEmail: "validate@example.com",
|
|
||||||
UserRole: acl.RoleAdmin.String(),
|
|
||||||
}
|
|
||||||
assert.Error(t, u.Validate())
|
|
||||||
})
|
|
||||||
t.Run("NameNotUnique", func(t *testing.T) {
|
t.Run("NameNotUnique", func(t *testing.T) {
|
||||||
FirstOrCreateUser(&User{
|
FirstOrCreateUser(&User{
|
||||||
UserName: "notunique1",
|
UserName: "notunique1",
|
||||||
|
|
|
@ -52,6 +52,12 @@ const (
|
||||||
IsUnstacked int8 = -1
|
IsUnstacked int8 = -1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Authentication providers.
|
||||||
|
const (
|
||||||
|
ProviderNone = ""
|
||||||
|
ProviderPassword = "password"
|
||||||
|
)
|
||||||
|
|
||||||
// Sort options.
|
// Sort options.
|
||||||
const (
|
const (
|
||||||
SortOrderDefault = ""
|
SortOrderDefault = ""
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
package query
|
package query
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/dustin/go-humanize/english"
|
"github.com/dustin/go-humanize/english"
|
||||||
"github.com/jinzhu/gorm"
|
"github.com/jinzhu/gorm"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/entity"
|
"github.com/photoprism/photoprism/internal/entity"
|
||||||
"github.com/photoprism/photoprism/internal/mutex"
|
"github.com/photoprism/photoprism/internal/mutex"
|
||||||
|
|
|
@ -4,18 +4,17 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/i18n"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"github.com/photoprism/photoprism/internal/config"
|
"github.com/photoprism/photoprism/internal/config"
|
||||||
|
"github.com/photoprism/photoprism/internal/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
// registerStaticRoutes configures serving static assets and templates.
|
// registerStaticRoutes configures serving static assets and templates.
|
||||||
func registerStaticRoutes(router *gin.Engine, conf *config.Config) {
|
func registerStaticRoutes(router *gin.Engine, conf *config.Config) {
|
||||||
// Redirects to the PWA for now, can be replaced by a template later.
|
// Redirects to the PWA for now, can be replaced by a template later.
|
||||||
router.GET(conf.BaseUri("/"), func(c *gin.Context) {
|
router.GET(conf.BaseUri("/"), func(c *gin.Context) {
|
||||||
c.Redirect(http.StatusTemporaryRedirect, conf.BaseUri("/library/login"))
|
c.Redirect(http.StatusTemporaryRedirect, conf.LoginUri())
|
||||||
})
|
})
|
||||||
|
|
||||||
// Shows "Page Not found" error if no other handler is registered.
|
// Shows "Page Not found" error if no other handler is registered.
|
||||||
|
|
Loading…
Reference in a new issue