1
0
Fork 0
mirror of https://github.com/Luzifer/nginx-sso.git synced 2024-10-18 07:34:22 +00:00

Modernize login dialog

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2019-04-22 19:43:56 +02:00
parent 1d3f88ff47
commit 930a23f461
Signed by: luzifer
GPG key ID: DC2729FDD34BE99E
4 changed files with 175 additions and 81 deletions

View file

@ -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
View file

@ -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"`

View file

@ -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
View 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
}