3
0

Add support for personalized auth screen

This commit is contained in:
kinyaelgrande 2022-11-01 11:36:16 +03:00 committed by Mumbi Francis
parent f2469f9353
commit 29262afadf
11 changed files with 329 additions and 42 deletions

View File

@ -16,6 +16,7 @@
v-if="!disabled"
:endpoint="endpoint"
:labels="labels"
:accepted-files="['image/*']"
@upload="$emit('upload', $event)"
/>
</b-form>

View File

@ -0,0 +1,146 @@
<template>
<b-card
data-test-id="card-edit-authentication"
header-bg-variant="white"
footer-bg-variant="white"
class="shadow-sm"
>
<template #header>
<h3 class="m-0">
{{ $t("title") }}
</h3>
</template>
<b-row cols="12">
<b-col cols="6">
<div class="shadow-sm">
<div class="d-flex justify-content-between">
<h5>
{{ $t("image.uploader.title") }}
</h5>
<b-button
v-if="uploadedFile('auth.ui.background-image-src')"
variant="link"
class="d-flex align-items-top text-dark p-1"
@click="$emit('resetAttachment', 'auth.ui.background-image-src')"
>
<font-awesome-icon :icon="['far', 'trash-alt']" />
</b-button>
</div>
<c-uploader-with-preview
:value="uploadedFile('auth.ui.background-image-src')"
:endpoint="'/settings/auth.ui.background-image-src'"
:disabled="!canManage"
:labels="$t('image.uploader', { returnObjects: true })"
@upload="$emit('onUpload')"
@clear="$emit('resetAttachment', 'auth.ui.background-image-src')"
/>
</div>
</b-col>
<b-col cols="6">
<div class="shadow-sm">
<h5>
{{ $t("image.editor.title") }}
</h5>
<ace-editor
data-test-id="auth-bg-image-styling-editor"
:font-size="14"
:show-print-margin="true"
:show-gutter="true"
:highlight-active-line="true"
width="100%"
height="200px"
mode="css"
theme="chrome"
name="editor/css"
:on-change="v => (settings['auth.ui.styles'] = v)"
:value="settings['auth.ui.styles']"
:editor-props="{
$blockScrolling: false
}"
/>
<c-submit-button
:disabled="!canManage"
:processing="processing"
:success="success"
class="float-right mt-2"
@submit="$emit('submit', settings['auth.ui.styles'])"
/>
</div>
</b-col>
</b-row>
</b-card>
</template>
<script>
import { Ace as AceEditor } from 'vue2-brace-editor'
import CUploaderWithPreview from 'corteza-webapp-admin/src/components/CUploaderWithPreview'
import CSubmitButton from 'corteza-webapp-admin/src/components/CSubmitButton'
import 'brace/mode/css'
import 'brace/theme/chrome'
export default {
name: 'CSystemEditorAuthBgImage',
i18nOptions: {
namespaces: 'system.settings',
keyPrefix: 'editor.bgScreen',
},
components: {
CUploaderWithPreview,
AceEditor,
CSubmitButton,
},
props: {
settings: {
type: Object,
required: true,
},
canManage: {
type: Boolean,
required: true,
},
processing: {
type: Boolean,
value: false,
},
success: {
type: Boolean,
value: false,
},
},
methods: {
uploadedFile (name) {
const localAttachment = /^attachment:(\d+)/
switch (true) {
case this.settings[name] && localAttachment.test(this.settings[name]):
const [, attachmentID] = localAttachment.exec(this.settings[name])
return (
this.$SystemAPI.baseURL +
this.$SystemAPI.attachmentOriginalEndpoint({
attachmentID,
kind: 'settings',
name,
})
)
}
return undefined
},
},
}
</script>

View File

@ -22,12 +22,22 @@
:can-manage="canManage"
@submit="onExternalSubmit"
/>
<c-system-editor-auth-bg-screen
:settings="getAuthBackground"
:can-manage="canManage"
class="mt-3"
@onUpload="onBackgroundImageUpload"
@resetAttachment="onResetBackgroundImage"
@submit="onAuthBackgroundSubmit"
/>
</b-container>
</template>
<script>
import editorHelpers from 'corteza-webapp-admin/src/mixins/editorHelpers'
import CSystemEditorAuth from 'corteza-webapp-admin/src/components/Settings/System/CSystemEditorAuth'
import CSystemEditorExternal from 'corteza-webapp-admin/src/components/Settings/System/CSystemEditorExternal'
import CSystemEditorAuthBgScreen from 'corteza-webapp-admin/src/components/Settings/System/CSystemEditorAuthBgScreen'
import { mapGetters } from 'vuex'
export default {
@ -39,6 +49,7 @@ export default {
components: {
CSystemEditorAuth,
CSystemEditorExternal,
CSystemEditorAuthBgScreen,
},
mixins: [
@ -58,6 +69,11 @@ export default {
processing: false,
success: false,
},
authBackground: {
processing: false,
success: false,
},
}
},
@ -71,18 +87,13 @@ export default {
},
getAuth () {
if (this.settings.length > 0) {
return this.settings.reduce((map, obj) => {
const { name, value } = obj
const split = name.split('.')
if (split[0] === 'auth' && split[1] !== 'external') {
map[name] = value
}
return map
}, {})
}
return {}
return this.filterSettings('auth')
},
getAuthBackground () {
return this.filterSettings('auth.ui')
},
},
created () {
@ -106,6 +117,19 @@ export default {
})
},
filterSettings (prefix) {
if (this.settings.length > 0) {
return this.settings.reduce((map, obj) => {
const { name, value } = obj
if (name.startsWith(prefix)) {
map[name] = value
}
return map
}, {})
}
return {}
},
onAuthSubmit (auth) {
this.auth.processing = true
@ -130,7 +154,9 @@ export default {
this.$SystemAPI.settingsUpdate({ values: external })
.then(() => {
this.animateSuccess('external')
this.toastSuccess(this.$t('notification:settings.system.external.success'))
this.toastSuccess(
this.$t('notification:settings.system.external.success')
)
})
.catch(this.toastErrorHandler(this.$t('notification:settings.system.external.error')))
.finally(() => {
@ -138,6 +164,39 @@ export default {
this.fetchSettings()
})
},
onBackgroundImageUpload () {
this.fetchSettings()
},
onResetBackgroundImage (name) {
this.$SystemAPI.settingsUpdate({ values: [{ name, value: undefined }], upload: {} })
.then(() => {
this.fetchSettings()
})
},
onAuthBackgroundSubmit (authBackground) {
this.authBackground.processing = true
const values = ([
{
name: 'auth.ui.styles',
value: authBackground,
},
])
this.$SystemAPI.settingsUpdate({ values })
.then(() => {
this.animateSuccess('authBackground')
this.toastSuccess(this.$t('notification:settings.system.bgScreen.style.success'))
this.$Settings.fetch()
})
.catch(this.toastErrorHandler(this.$t('notification:settings.system.bgScreen.style.error')))
.finally(() => {
this.authBackground.processing = false
})
},
},
}
</script>

View File

@ -231,6 +231,10 @@ settings:
external:
success: External settings updated
error: External settings update failed
bgScreen:
style:
success: Auth background styles updated
error: Auth background styles update failed
compose:
fetch:
error: Compose settings fetch failed

View File

@ -122,3 +122,15 @@ editor:
permitted-roles:
description: Only roles in this list will be added into security context when authenticates with this provider
label: Permitted roles
bgScreen:
title: Auth Background Screen
image:
uploader:
title: Background Image
instructions: Click or drop background image here to upload
uploading: Uploading auth background image
editor:
title: Styles editor

View File

@ -6,6 +6,7 @@ import (
"fmt"
"net/url"
"os"
"regexp"
"strings"
"time"
@ -611,6 +612,10 @@ func updateAuthSettings(svc authServicer, current *types.AppSettings) {
Enforced: current.Auth.MultiFactor.EmailOTP.Enforced,
},
},
BackgroundUI: authSettings.BackgroundUI{
BackgroundImageSrcUrl: setAuthBgImageSrcUrl(current.Auth.UI.BackgroundImageSrc),
Styles: setAuthBgStyles(current.Auth.UI.Styles),
},
}
for _, p := range current.Auth.External.Providers {
@ -880,3 +885,25 @@ func setupSmtpDialer(log *zap.Logger, servers ...types.SmtpServers) {
)
}
func setAuthBgImageSrcUrl(imgAttachment string) string {
if imgAttachment == "" {
return ""
}
imgAttachmentValues := strings.Split(imgAttachment, ":")
imgSrcUrl := fmt.Sprintf("/api/system/%s/settings/%s/original/auth.ui.background-image-src", imgAttachmentValues[0], imgAttachmentValues[1])
return imgSrcUrl
}
func setAuthBgStyles(styles string) string {
re := regexp.MustCompile(`\{(.*?)\}`)
styles = strings.Replace(styles, "\n", "", -1)
matches := re.FindAllStringSubmatch(styles, -1)
if matches != nil {
return matches[0][1]
}
return ""
}

View File

@ -1,32 +1,44 @@
<!doctype html>
<html lang="{{ language }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap core CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.3.0/font/bootstrap-icons.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@500;700&display=swap" rel="stylesheet">
<!-- Custom CSS -->
<link href="{{ links.AuthAssets }}/style.css?{{ buildtime }}" rel="stylesheet">
<title>Corteza</title>
</head>
<body style="background: url({{ links.Assets }}/release-background.png) no-repeat top;background-size: cover;background-attachment: fixed;">
<header>
{{ if .user }}
<div class="float-right text-white m-2">
<a class="font-weight-bold text-white" href="{{ links.Base }}"><i class="bi bi-grid-3x2-gap-fill text-white mr-1 align-middle" style="font-size: 1.4rem;"></i></a>
{{ tr "inc_header.logged-in-as" }}
<a data-test-id="link-redirect-to-profile" class="font-weight-bold text-white" href="{{ links.Profile }}">{{ coalesce .user.Name .user.Handle .user.Email }}</a>
|
<a data-test-id="link-logout" class="font-weight-bold text-white" href="{{ links.Logout }}">{{ tr "inc_header.logout" }}</a>
</div>
{{ end }}
</header>
<main class="auth mt-sm-5">
<div class="card">
{{ template "inc_nav.html.tpl" . }}
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap core CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.3.0/font/bootstrap-icons.css">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@500;700&display=swap" rel="stylesheet">
<!-- Custom CSS -->
<link href="{{ links.AuthAssets }}/style.css?{{ buildtime }}" rel="stylesheet">
<title>Corteza</title>
<style>
body {
{{ safeCSS .authBg }}
background-size: cover;
background-attachment: fixed;
}
</style>
</head>
<body>
<header>
{{ if .user }}
<div class="float-right text-white m-2">
<a class="font-weight-bold text-white" href="{{ links.Base }}"><i
class="bi bi-grid-3x2-gap-fill text-white mr-1 align-middle" style="font-size: 1.4rem;"></i></a>
{{ tr "inc_header.logged-in-as" }}
<a data-test-id="link-redirect-to-profile" class="font-weight-bold text-white"
href="{{ links.Profile }}">{{ coalesce .user.Name .user.Handle .user.Email }}</a>
|
<a data-test-id="link-logout" class="font-weight-bold text-white"
href="{{ links.Logout }}">{{ tr "inc_header.logout" }}</a>
</div>
{{ end }}
</header>
<main class="auth mt-sm-5">
<div class="card">
{{ template "inc_nav.html.tpl" . }}

View File

@ -143,6 +143,7 @@ func New(ctx context.Context, log *zap.Logger, oa2m oauth2def.Manager, s store.S
// temp, will be replaced
"language": func() string { return language.Tag{}.String() },
"tr": func(key string, pp ...string) string { return key },
"safeCSS": func(styles string) template.CSS { return template.CSS(styles) },
})
useEmbedded = len(opt.AssetsPath) == 0

View File

@ -3,6 +3,7 @@ package handlers
import (
"context"
"encoding/gob"
"fmt"
"html/template"
"io"
"net/http"
@ -305,7 +306,7 @@ func (h *AuthHandlers) handle(fn handlerFn) http.HandlerFunc {
}
}
// Add alerts, settings, providers, csrf token
// Add alerts, settings, providers, csrf token, Bg
func (h *AuthHandlers) enrichTmplData(req *request.AuthReq) interface{} {
d := req.Data
if req.AuthUser != nil {
@ -362,9 +363,21 @@ func (h *AuthHandlers) enrichTmplData(req *request.AuthReq) interface{} {
dSettings.Providers = nil
d["settings"] = dSettings
d["authBg"] = h.bgStylesData()
return d
}
func (h *AuthHandlers) bgStylesData() string {
if h.Settings.BackgroundUI.BackgroundImageSrcUrl == "" {
return fmt.Sprintf("background: url(%s/release-background.png) no-repeat top; %s",
GetLinks().Assets, h.Settings.BackgroundUI.Styles)
}
return fmt.Sprintf("background: url('%s') no-repeat top; %s",
h.Settings.BackgroundUI.BackgroundImageSrcUrl, h.Settings.BackgroundUI.Styles)
}
// Handle successful auth (on any factor)
func handleSuccessfulAuth(req *request.AuthReq) {
switch {

View File

@ -12,6 +12,7 @@ type (
Providers []Provider
Saml SAML
MultiFactor MultiFactor
BackgroundUI BackgroundUI
}
SAML struct {
@ -79,4 +80,9 @@ type (
Secret string
Scope string
}
BackgroundUI struct {
BackgroundImageSrcUrl string
Styles string
}
)

View File

@ -148,6 +148,12 @@ type (
FromAddress string `kv:"from-address"`
FromName string `kv:"from-name"`
} `json:"-"`
//Auth Background Image settings
UI struct {
BackgroundImageSrc string `kv:"background-image-src" json:"backgroundImageSrc"`
Styles string `kv:"styles" json:"styles"`
} `kv:"ui" json:"ui"`
} `json:"auth"`
Compose struct {