mirror of
https://github.com/Luzifer/nginx-sso.git
synced 2025-01-03 11:36:02 +00:00
Modernize login dialog
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
1d3f88ff47
commit
930a23f461
4 changed files with 175 additions and 81 deletions
|
@ -8,84 +8,84 @@
|
||||||
<title>{{ login.Title }}</title>
|
<title>{{ login.Title }}</title>
|
||||||
|
|
||||||
<!-- Bootstrap -->
|
<!-- Bootstrap -->
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/css/bootstrap.min.css"
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@4.3.1/dist/sandstone/bootstrap.min.css"
|
||||||
integrity="sha256-bZLfwXAP04zRMK2BjiO8iu9pf4FbLqX6zitd+tIvLhE=" crossorigin="anonymous">
|
integrity="sha256-qgpZ1V8XkWmm9APL5rLtRW+Tyhp+0TPKJm4JMprrSOw=" crossorigin="anonymous">
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
html, body, .container, .row { height: 100%; }
|
html, body {
|
||||||
.vertical-align { display: flex; flex-direction: column; justify-content: center; }
|
background-color: #f2f2f2;
|
||||||
.modal-content { background-color: darkcyan; }
|
height: 100%;
|
||||||
.modal-heading h2 { color: white; }
|
margin: 0;
|
||||||
.nav-tabs>li>a { color: white; }
|
padding: 0;
|
||||||
.nav-tabs>li.active>a, .nav-tabs>li>a:hover { color: #333; }
|
}
|
||||||
.tab-pane { padding-top: 10px; color: white; }
|
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
{% verbatim %}
|
||||||
|
<div class="container h-100">
|
||||||
|
|
||||||
<div class="row vertical-align">
|
<div class="row h-100 justify-content-center align-items-center">
|
||||||
<div class="col-md-offset-2 col-md-8">
|
|
||||||
|
|
||||||
<div class="modal-dialog">
|
<div>
|
||||||
<div class="modal-content">
|
<div class="col-12 text-center mb-3">
|
||||||
<div class="modal-heading">
|
<h1>{{ login.title }}</h1>
|
||||||
<h2 class="text-center">{{ login.Title }}</h2>
|
</div>
|
||||||
</div>
|
<div class="card" style="width: 30rem;">
|
||||||
<hr>
|
<div class="card-header" v-if="loginMethods.length > 1">
|
||||||
<div class="modal-body">
|
|
||||||
|
|
||||||
<!-- Nav tabs -->
|
<div class="input-group">
|
||||||
{% if active_methods | length > 1 %}
|
<div class="input-group-prepend">
|
||||||
<ul class="nav nav-tabs" role="tablist">
|
<div class="input-group-text">Login with</div>
|
||||||
{% for method in active_methods sorted %}
|
|
||||||
<li role="presentation" class="{% if method == login.DefaultMethod %}active{% endif %}">
|
|
||||||
{% for name, desc in login.Names %}{% if method == name %}
|
|
||||||
<a href="#{{ method }}" aria-controls="{{ method }}" role="tab" data-toggle="tab">{{ desc }}</a>
|
|
||||||
{% endif %}{% endfor %}
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<!-- Tab panes -->
|
|
||||||
<div class="tab-content">
|
|
||||||
{% for method, fields in active_methods sorted %}
|
|
||||||
<div role="tabpanel" class="tab-pane {% if method == login.DefaultMethod %}active{% endif %}" id="{{ method }}">
|
|
||||||
<form action="/login" method="post">
|
|
||||||
{% for field in fields %}
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="{{ method }}-{{ field.Name }}">{{ field.Label }}</label>
|
|
||||||
<input
|
|
||||||
class="form-control"
|
|
||||||
id="{{ method }}-{{ field.Name }}"
|
|
||||||
name="{{ method }}-{{ field.Name }}"
|
|
||||||
{% if field.Action %}onclick="{{ field.Action }}"{% endif %}
|
|
||||||
{% if field.Type == "button" %}
|
|
||||||
value="{{ field.Placeholder }}"
|
|
||||||
{% else %}
|
|
||||||
placeholder="{{ field.Placeholder }}"
|
|
||||||
{% endif %}
|
|
||||||
type="{{ field.Type }}"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
|
|
||||||
<div class="form-group text-center">
|
|
||||||
<button type="submit" class="btn btn-success btn-lg">Login</button>
|
|
||||||
<input type="hidden" name="go" value="{{ go }}">
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
</div>
|
||||||
|
<select class="form-control" v-model="active_method">
|
||||||
|
<option
|
||||||
|
:key="method.name"
|
||||||
|
:value="method.name"
|
||||||
|
v-for="method in loginMethods"
|
||||||
|
>
|
||||||
|
{{ method.label }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div> <!-- /.panel-body -->
|
</div> <!-- ./card-header -->
|
||||||
</div> <!-- /.modal-content -->
|
<div class="card-body">
|
||||||
</div> <!-- /.modal-dialog -->
|
|
||||||
|
|
||||||
</div> <!-- /.col-md-8 -->
|
<div class="card-text">
|
||||||
</div> <!-- /.row -->
|
<form action="/login" method="post">
|
||||||
|
|
||||||
|
<input type="hidden" name="go" :value="go">
|
||||||
|
<div
|
||||||
|
class="form-group"
|
||||||
|
:key="field.name"
|
||||||
|
v-for="field in activeFields"
|
||||||
|
>
|
||||||
|
<label :for="`${active_method}-${field.name}`" v-if="field.label">
|
||||||
|
{{ field.label }}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
:class="fieldClasses(field)"
|
||||||
|
:onclick="field.action"
|
||||||
|
:id="`${active_method}-${field.name}`"
|
||||||
|
:name="`${active_method}-${field.name}`"
|
||||||
|
:placeholder="field.placeholder"
|
||||||
|
:type="field.type"
|
||||||
|
:value="field.type == 'button' ? field.placeholder : ''"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group text-center" v-if="needsLoginButton">
|
||||||
|
<button type="submit" class="btn btn-success btn-lg">Login</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
</div> <!-- /.container -->
|
</div> <!-- /.container -->
|
||||||
|
|
||||||
|
@ -93,12 +93,78 @@
|
||||||
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.0/dist/jquery.min.js"
|
<script src="https://cdn.jsdelivr.net/npm/jquery@3.4.0/dist/jquery.min.js"
|
||||||
integrity="sha256-BJeo0qm959uMBGb65z40ejJYGSgR7REI4+CW1fNKwOg=" crossorigin="anonymous"></script>
|
integrity="sha256-BJeo0qm959uMBGb65z40ejJYGSgR7REI4+CW1fNKwOg=" crossorigin="anonymous"></script>
|
||||||
<!-- Include all compiled plugins (below), or include individual files as needed -->
|
<!-- Include all compiled plugins (below), or include individual files as needed -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@3.4.1/dist/js/bootstrap.min.js"
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.3.1/dist/js/bootstrap.min.js"
|
||||||
integrity="sha256-nuL8/2cJ5NDSSwnKD8VqreErSWHtnEP9E7AySL+1ev4=" crossorigin="anonymous"></script>
|
integrity="sha256-CjSoeELFOcH0/uxWu6mC/Vlrc1AARqbm/jiiImDGV3s=" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.min.js"\
|
||||||
|
integrity="sha256-chlNFSVx3TdcQ2Xlw7SvnbLAavAQLO0Y/LBiWX04viY=" crossorigin="anonymous"></script>
|
||||||
|
|
||||||
|
{% endverbatim %}
|
||||||
<script>
|
<script>
|
||||||
$('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
|
function sortOrder(i, j) {
|
||||||
$(e.target.hash).find('input:first').focus();
|
switch (true) {
|
||||||
|
case i < j:
|
||||||
|
return -1
|
||||||
|
case j < i:
|
||||||
|
return 1
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var app = new Vue({
|
||||||
|
computed: {
|
||||||
|
activeFields() {
|
||||||
|
return this.available_methods[this.active_method]
|
||||||
|
},
|
||||||
|
|
||||||
|
loginMethods() {
|
||||||
|
var res = []
|
||||||
|
|
||||||
|
for (name in this.available_methods) {
|
||||||
|
res.push({
|
||||||
|
name,
|
||||||
|
label: this.login.names[name],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
res.sort(function(i, j) { return sortOrder(i.name, j.name) })
|
||||||
|
|
||||||
|
return res
|
||||||
|
},
|
||||||
|
|
||||||
|
needsLoginButton() {
|
||||||
|
for (idx in this.activeFields) {
|
||||||
|
var field = this.activeFields[idx]
|
||||||
|
if (field.type == 'button') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
data: {
|
||||||
|
active_method: '{{ login.DefaultMethod }}',
|
||||||
|
available_methods: {{ active_methods | to_json | safe }},
|
||||||
|
go: '{{ go }}',
|
||||||
|
login: {{ login | to_json | safe }},
|
||||||
|
},
|
||||||
|
|
||||||
|
el: ".container",
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
fieldClasses(field) {
|
||||||
|
var classes = ['form-control']
|
||||||
|
|
||||||
|
if (field.type == 'button') {
|
||||||
|
classes.push('btn')
|
||||||
|
classes.push('btn-success')
|
||||||
|
}
|
||||||
|
|
||||||
|
return classes.join(' ')
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
10
main.go
10
main.go
|
@ -31,11 +31,11 @@ type mainConfig struct {
|
||||||
Port int `yaml:"port"`
|
Port int `yaml:"port"`
|
||||||
} `yaml:"listen"`
|
} `yaml:"listen"`
|
||||||
Login struct {
|
Login struct {
|
||||||
Title string `yaml:"title"`
|
Title string `yaml:"title" json:"title"`
|
||||||
DefaultMethod string `yaml:"default_method"`
|
DefaultMethod string `yaml:"default_method" json:"default_method"`
|
||||||
DefaultRedirect string `yaml:"default_redirect"`
|
DefaultRedirect string `yaml:"default_redirect" json:"default_redirect"`
|
||||||
HideMFAField bool `yaml:"hide_mfa_field"`
|
HideMFAField bool `yaml:"hide_mfa_field" json:"hide_mfa_field"`
|
||||||
Names map[string]string `yaml:"names"`
|
Names map[string]string `yaml:"names" json:"names"`
|
||||||
} `yaml:"login"`
|
} `yaml:"login"`
|
||||||
Plugins struct {
|
Plugins struct {
|
||||||
Directory string `yaml:"directory"`
|
Directory string `yaml:"directory"`
|
||||||
|
|
|
@ -48,9 +48,9 @@ type Authenticator interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type LoginField struct {
|
type LoginField struct {
|
||||||
Action string
|
Action string `json:"action"`
|
||||||
Label string
|
Label string `json:"label"`
|
||||||
Name string
|
Name string `json:"name"`
|
||||||
Placeholder string
|
Placeholder string `json:"placeholder"`
|
||||||
Type string
|
Type string `json:"type"`
|
||||||
}
|
}
|
||||||
|
|
28
pongo.go
Normal file
28
pongo.go
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/flosch/pongo2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
pongo2.RegisterFilter("to_json", filterToJSON)
|
||||||
|
}
|
||||||
|
|
||||||
|
func filterToJSON(in *pongo2.Value, param *pongo2.Value) (*pongo2.Value, *pongo2.Error) {
|
||||||
|
var buf = new(bytes.Buffer)
|
||||||
|
|
||||||
|
err := json.NewEncoder(buf).Encode(in.Interface())
|
||||||
|
if err != nil {
|
||||||
|
return nil, &pongo2.Error{
|
||||||
|
Sender: "to_json",
|
||||||
|
OrigError: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result := strings.TrimSpace(buf.String())
|
||||||
|
return pongo2.AsValue(result), nil
|
||||||
|
}
|
Loading…
Reference in a new issue