Auth-related stuffs

This commit is contained in:
Timur Ismagilov 2024-07-28 12:15:59 +03:00
parent ea1ae0d078
commit 6d2df119d8
15 changed files with 345 additions and 905 deletions

View File

@ -1,143 +0,0 @@
{% import "net/http" %}
{% import "github.com/bouncepaw/mycorrhiza/internal/cfg" %}
{% import "github.com/bouncepaw/mycorrhiza/l18n" %}
{% func Register(rq *http.Request) %}
{% code
lc := l18n.FromRequest(rq)
%}
<main class="main-width">
<section>
{% if cfg.AllowRegistration %}
<form class="modal" method="post" action="/register?{%s rq.URL.RawQuery %}" id="register-form" enctype="multipart/form-data" autocomplete="off">
<fieldset class="modal__fieldset">
<legend class="modal__title">{%s lc.Get("auth.register_header", &l18n.Replacements{"name": cfg.WikiName}) %}</legend>
<label for="register-form__username">{%s lc.Get("auth.username") %}</label>
<br>
<input type="text" required autofocus id="login-form__username" name="username">
<br>
<label for="login-form__password">{%s lc.Get("auth.password") %}</label>
<br>
<input type="password" required name="password">
<p>{%s lc.Get("auth.password_tip") %}</p>
<p>{%s lc.Get("auth.cookie_tip") %}</p>
<button class="btn" type="submit" value="Register">{%s lc.Get("auth.register_button") %}</button>
<a class="btn btn_weak" href="/{%s rq.URL.RawQuery %}">{%s lc.Get("ui.cancel") %}</a>
</fieldset>
</form>
{%= telegramWidget(lc) %}
{% elseif cfg.UseAuth %}
<p>{%s lc.Get("auth.noregister") %}</p>
<p><a href="/{%s rq.URL.RawQuery %}">← {%s lc.Get("auth.go_back") %}</a></p>
{% else %}
<p>{%s lc.Get("auth.noauth") %}</p>
<p><a href="/{%s rq.URL.RawQuery %}">← {%s lc.Get("auth.go_back") %}</a></p>
{% endif %}
</section>
</main>
{% endfunc %}
{% func Login(lc *l18n.Localizer) %}
<main class="main-width">
<section>
{% if cfg.UseAuth %}
<form class="modal" method="post" action="/login" id="login-form" enctype="multipart/form-data" autocomplete="on">
<fieldset class="modal__fieldset">
<legend class="modal__title">{%s lc.Get("auth.login_header", &l18n.Replacements{"name": cfg.WikiName}) %}</legend>
<label for="login-form__username">{%s lc.Get("auth.username") %}</label>
<br>
<input type="text" required autofocus id="login-form__username" name="username" autocomplete="username">
<br>
<label for="login-form__password">{%s lc.Get("auth.password") %}</label>
<br>
<input type="password" required name="password" autocomplete="current-password">
<p>{%s lc.Get("auth.cookie_tip") %}</p>
<button class="btn" type="submit" value="Log in">{%s lc.Get("auth.login_button") %}</button>
<a class="btn btn_weak" href="/">{%s lc.Get("ui.cancel") %}</a>
</fieldset>
</form>
{%= telegramWidget(lc) %}
{% else %}
<p>{%s lc.Get("auth.noauth") %}</p>
<p><a class="btn btn_weak" href="/">← {%s lc.Get("auth.go_home") %}</a></p>
{% endif %}
</section>
</main>
{% endfunc %}
Telegram auth widget was requested by Yogurt. As you can see, we don't offer user administrators control over it. Of course we don't.
{% func telegramWidget(lc *l18n.Localizer) %}
{% if cfg.TelegramEnabled %}
<p class="telegram-notice">{%s lc.Get("auth.telegram_tip") %}</p>
<script async src="https://telegram.org/js/telegram-widget.js?15" data-telegram-login="{%s cfg.TelegramBotName %}" data-size="medium" data-userpic="false" data-auth-url="{%s cfg.URL %}/telegram-login"></script>
{% endif %}
{% endfunc %}
{% func LoginError(err string, lc *l18n.Localizer) %}
<main class="main-width">
<section>
{% switch err %}
{% case "unknown username" %}
<p class="error">{%s lc.Get("auth.error_username") %}</p>
{% case "wrong password" %}
<p class="error">{%s lc.Get("auth.error_password") %}</p>
{% default %}
<p class="error">{%s err %}</p>
{% endswitch %}
<p><a href="/login">← {%s lc.Get("auth.try_again") %}</a></p>
</section>
</main>
{% endfunc %}
{% func Logout(can bool, lc *l18n.Localizer) %}
<main class="main-width">
<section>
{% if can %}
<h1>{%s lc.Get("auth.logout_header") %}</h1>
<form method="POST" action="/logout">
<input class="btn btn_accent" type="submit" value="{%s lc.Get("auth.logout_button") %}"/>
<a class="btn btn_weak" href="/">{%s lc.Get("auth.go_home") %}</a>
</form>
{% else %}
<p>{%s lc.Get("auth.logout_anon") %}</p>
<p><a href="/login">{%s lc.Get("auth.login_title") %}</a></p>
<p><a href="/">← {%s lc.Get("auth.go_home") %}</a></p>
{% endif %}
</section>
</main>
{% endfunc %}
{% func Lock(lc *l18n.Localizer) %}
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>🔒 {%s lc.Get("auth.lock_title") %}</title>
<link rel="shortcut icon" href="/static/favicon.ico">
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<main class="locked-notice main-width">
<section class="locked-notice__message">
<p class="locked-notice__lock">🔒</p>
<h1 class="locked-notice__title">{%s lc.Get("auth.lock_title") %}</h1>
<form class="locked-notice__login-form" method="post" action="/login" id="login-form" enctype="multipart/form-data" autocomplete="on">
<label for="login-form__username">{%s lc.Get("auth.username") %}</label>
<br>
<input type="text" required autofocus id="login-form__username" name="username" autocomplete="username">
<br>
<label for="login-form__password">{%s lc.Get("auth.password") %}</label>
<br>
<input type="password" required name="password" autocomplete="current-password">
<br>
<button class="btn" type="submit" value="Log in">{%s lc.Get("auth.login_button") %}</button>
</form>
{%= telegramWidget(lc) %}
</section>
</main>
</body>
</html>
{% endfunc %}

View File

@ -1,597 +0,0 @@
// Code generated by qtc from "auth.qtpl". DO NOT EDIT.
// See https://github.com/valyala/quicktemplate for details.
//line auth/auth.qtpl:1
package auth
//line auth/auth.qtpl:1
import "net/http"
//line auth/auth.qtpl:2
import "github.com/bouncepaw/mycorrhiza/internal/cfg"
//line auth/auth.qtpl:3
import "github.com/bouncepaw/mycorrhiza/l18n"
//line auth/auth.qtpl:5
import (
qtio422016 "io"
qt422016 "github.com/valyala/quicktemplate"
)
//line auth/auth.qtpl:5
var (
_ = qtio422016.Copy
_ = qt422016.AcquireByteBuffer
)
//line auth/auth.qtpl:5
func StreamRegister(qw422016 *qt422016.Writer, rq *http.Request) {
//line auth/auth.qtpl:5
qw422016.N().S(`
`)
//line auth/auth.qtpl:7
lc := l18n.FromRequest(rq)
//line auth/auth.qtpl:8
qw422016.N().S(`
<main class="main-width">
<section>
`)
//line auth/auth.qtpl:11
if cfg.AllowRegistration {
//line auth/auth.qtpl:11
qw422016.N().S(`
<form class="modal" method="post" action="/register?`)
//line auth/auth.qtpl:12
qw422016.E().S(rq.URL.RawQuery)
//line auth/auth.qtpl:12
qw422016.N().S(`" id="register-form" enctype="multipart/form-data" autocomplete="off">
<fieldset class="modal__fieldset">
<legend class="modal__title">`)
//line auth/auth.qtpl:14
qw422016.E().S(lc.Get("auth.register_header", &l18n.Replacements{"name": cfg.WikiName}))
//line auth/auth.qtpl:14
qw422016.N().S(`</legend>
<label for="register-form__username">`)
//line auth/auth.qtpl:16
qw422016.E().S(lc.Get("auth.username"))
//line auth/auth.qtpl:16
qw422016.N().S(`</label>
<br>
<input type="text" required autofocus id="login-form__username" name="username">
<br>
<label for="login-form__password">`)
//line auth/auth.qtpl:20
qw422016.E().S(lc.Get("auth.password"))
//line auth/auth.qtpl:20
qw422016.N().S(`</label>
<br>
<input type="password" required name="password">
<p>`)
//line auth/auth.qtpl:23
qw422016.E().S(lc.Get("auth.password_tip"))
//line auth/auth.qtpl:23
qw422016.N().S(`</p>
<p>`)
//line auth/auth.qtpl:24
qw422016.E().S(lc.Get("auth.cookie_tip"))
//line auth/auth.qtpl:24
qw422016.N().S(`</p>
<button class="btn" type="submit" value="Register">`)
//line auth/auth.qtpl:25
qw422016.E().S(lc.Get("auth.register_button"))
//line auth/auth.qtpl:25
qw422016.N().S(`</button>
<a class="btn btn_weak" href="/`)
//line auth/auth.qtpl:26
qw422016.E().S(rq.URL.RawQuery)
//line auth/auth.qtpl:26
qw422016.N().S(`">`)
//line auth/auth.qtpl:26
qw422016.E().S(lc.Get("ui.cancel"))
//line auth/auth.qtpl:26
qw422016.N().S(`</a>
</fieldset>
</form>
`)
//line auth/auth.qtpl:29
streamtelegramWidget(qw422016, lc)
//line auth/auth.qtpl:29
qw422016.N().S(`
`)
//line auth/auth.qtpl:30
} else if cfg.UseAuth {
//line auth/auth.qtpl:30
qw422016.N().S(`
<p>`)
//line auth/auth.qtpl:31
qw422016.E().S(lc.Get("auth.noregister"))
//line auth/auth.qtpl:31
qw422016.N().S(`</p>
<p><a href="/`)
//line auth/auth.qtpl:32
qw422016.E().S(rq.URL.RawQuery)
//line auth/auth.qtpl:32
qw422016.N().S(`">← `)
//line auth/auth.qtpl:32
qw422016.E().S(lc.Get("auth.go_back"))
//line auth/auth.qtpl:32
qw422016.N().S(`</a></p>
`)
//line auth/auth.qtpl:33
} else {
//line auth/auth.qtpl:33
qw422016.N().S(`
<p>`)
//line auth/auth.qtpl:34
qw422016.E().S(lc.Get("auth.noauth"))
//line auth/auth.qtpl:34
qw422016.N().S(`</p>
<p><a href="/`)
//line auth/auth.qtpl:35
qw422016.E().S(rq.URL.RawQuery)
//line auth/auth.qtpl:35
qw422016.N().S(`">← `)
//line auth/auth.qtpl:35
qw422016.E().S(lc.Get("auth.go_back"))
//line auth/auth.qtpl:35
qw422016.N().S(`</a></p>
`)
//line auth/auth.qtpl:36
}
//line auth/auth.qtpl:36
qw422016.N().S(`
</section>
</main>
`)
//line auth/auth.qtpl:39
}
//line auth/auth.qtpl:39
func WriteRegister(qq422016 qtio422016.Writer, rq *http.Request) {
//line auth/auth.qtpl:39
qw422016 := qt422016.AcquireWriter(qq422016)
//line auth/auth.qtpl:39
StreamRegister(qw422016, rq)
//line auth/auth.qtpl:39
qt422016.ReleaseWriter(qw422016)
//line auth/auth.qtpl:39
}
//line auth/auth.qtpl:39
func Register(rq *http.Request) string {
//line auth/auth.qtpl:39
qb422016 := qt422016.AcquireByteBuffer()
//line auth/auth.qtpl:39
WriteRegister(qb422016, rq)
//line auth/auth.qtpl:39
qs422016 := string(qb422016.B)
//line auth/auth.qtpl:39
qt422016.ReleaseByteBuffer(qb422016)
//line auth/auth.qtpl:39
return qs422016
//line auth/auth.qtpl:39
}
//line auth/auth.qtpl:41
func StreamLogin(qw422016 *qt422016.Writer, lc *l18n.Localizer) {
//line auth/auth.qtpl:41
qw422016.N().S(`
<main class="main-width">
<section>
`)
//line auth/auth.qtpl:44
if cfg.UseAuth {
//line auth/auth.qtpl:44
qw422016.N().S(`
<form class="modal" method="post" action="/login" id="login-form" enctype="multipart/form-data" autocomplete="on">
<fieldset class="modal__fieldset">
<legend class="modal__title">`)
//line auth/auth.qtpl:47
qw422016.E().S(lc.Get("auth.login_header", &l18n.Replacements{"name": cfg.WikiName}))
//line auth/auth.qtpl:47
qw422016.N().S(`</legend>
<label for="login-form__username">`)
//line auth/auth.qtpl:48
qw422016.E().S(lc.Get("auth.username"))
//line auth/auth.qtpl:48
qw422016.N().S(`</label>
<br>
<input type="text" required autofocus id="login-form__username" name="username" autocomplete="username">
<br>
<label for="login-form__password">`)
//line auth/auth.qtpl:52
qw422016.E().S(lc.Get("auth.password"))
//line auth/auth.qtpl:52
qw422016.N().S(`</label>
<br>
<input type="password" required name="password" autocomplete="current-password">
<p>`)
//line auth/auth.qtpl:55
qw422016.E().S(lc.Get("auth.cookie_tip"))
//line auth/auth.qtpl:55
qw422016.N().S(`</p>
<button class="btn" type="submit" value="Log in">`)
//line auth/auth.qtpl:56
qw422016.E().S(lc.Get("auth.login_button"))
//line auth/auth.qtpl:56
qw422016.N().S(`</button>
<a class="btn btn_weak" href="/">`)
//line auth/auth.qtpl:57
qw422016.E().S(lc.Get("ui.cancel"))
//line auth/auth.qtpl:57
qw422016.N().S(`</a>
</fieldset>
</form>
`)
//line auth/auth.qtpl:60
streamtelegramWidget(qw422016, lc)
//line auth/auth.qtpl:60
qw422016.N().S(`
`)
//line auth/auth.qtpl:61
} else {
//line auth/auth.qtpl:61
qw422016.N().S(`
<p>`)
//line auth/auth.qtpl:62
qw422016.E().S(lc.Get("auth.noauth"))
//line auth/auth.qtpl:62
qw422016.N().S(`</p>
<p><a class="btn btn_weak" href="/"> `)
//line auth/auth.qtpl:63
qw422016.E().S(lc.Get("auth.go_home"))
//line auth/auth.qtpl:63
qw422016.N().S(`</a></p>
`)
//line auth/auth.qtpl:64
}
//line auth/auth.qtpl:64
qw422016.N().S(`
</section>
</main>
`)
//line auth/auth.qtpl:67
}
//line auth/auth.qtpl:67
func WriteLogin(qq422016 qtio422016.Writer, lc *l18n.Localizer) {
//line auth/auth.qtpl:67
qw422016 := qt422016.AcquireWriter(qq422016)
//line auth/auth.qtpl:67
StreamLogin(qw422016, lc)
//line auth/auth.qtpl:67
qt422016.ReleaseWriter(qw422016)
//line auth/auth.qtpl:67
}
//line auth/auth.qtpl:67
func Login(lc *l18n.Localizer) string {
//line auth/auth.qtpl:67
qb422016 := qt422016.AcquireByteBuffer()
//line auth/auth.qtpl:67
WriteLogin(qb422016, lc)
//line auth/auth.qtpl:67
qs422016 := string(qb422016.B)
//line auth/auth.qtpl:67
qt422016.ReleaseByteBuffer(qb422016)
//line auth/auth.qtpl:67
return qs422016
//line auth/auth.qtpl:67
}
// Telegram auth widget was requested by Yogurt. As you can see, we don't offer user administrators control over it. Of course we don't.
//line auth/auth.qtpl:70
func streamtelegramWidget(qw422016 *qt422016.Writer, lc *l18n.Localizer) {
//line auth/auth.qtpl:70
qw422016.N().S(`
`)
//line auth/auth.qtpl:71
if cfg.TelegramEnabled {
//line auth/auth.qtpl:71
qw422016.N().S(`
<p class="telegram-notice">`)
//line auth/auth.qtpl:72
qw422016.E().S(lc.Get("auth.telegram_tip"))
//line auth/auth.qtpl:72
qw422016.N().S(`</p>
<script async src="https://telegram.org/js/telegram-widget.js?15" data-telegram-login="`)
//line auth/auth.qtpl:73
qw422016.E().S(cfg.TelegramBotName)
//line auth/auth.qtpl:73
qw422016.N().S(`" data-size="medium" data-userpic="false" data-auth-url="`)
//line auth/auth.qtpl:73
qw422016.E().S(cfg.URL)
//line auth/auth.qtpl:73
qw422016.N().S(`/telegram-login"></script>
`)
//line auth/auth.qtpl:74
}
//line auth/auth.qtpl:74
qw422016.N().S(`
`)
//line auth/auth.qtpl:75
}
//line auth/auth.qtpl:75
func writetelegramWidget(qq422016 qtio422016.Writer, lc *l18n.Localizer) {
//line auth/auth.qtpl:75
qw422016 := qt422016.AcquireWriter(qq422016)
//line auth/auth.qtpl:75
streamtelegramWidget(qw422016, lc)
//line auth/auth.qtpl:75
qt422016.ReleaseWriter(qw422016)
//line auth/auth.qtpl:75
}
//line auth/auth.qtpl:75
func telegramWidget(lc *l18n.Localizer) string {
//line auth/auth.qtpl:75
qb422016 := qt422016.AcquireByteBuffer()
//line auth/auth.qtpl:75
writetelegramWidget(qb422016, lc)
//line auth/auth.qtpl:75
qs422016 := string(qb422016.B)
//line auth/auth.qtpl:75
qt422016.ReleaseByteBuffer(qb422016)
//line auth/auth.qtpl:75
return qs422016
//line auth/auth.qtpl:75
}
//line auth/auth.qtpl:77
func StreamLoginError(qw422016 *qt422016.Writer, err string, lc *l18n.Localizer) {
//line auth/auth.qtpl:77
qw422016.N().S(`
<main class="main-width">
<section>
`)
//line auth/auth.qtpl:80
switch err {
//line auth/auth.qtpl:81
case "unknown username":
//line auth/auth.qtpl:81
qw422016.N().S(`
<p class="error">`)
//line auth/auth.qtpl:82
qw422016.E().S(lc.Get("auth.error_username"))
//line auth/auth.qtpl:82
qw422016.N().S(`</p>
`)
//line auth/auth.qtpl:83
case "wrong password":
//line auth/auth.qtpl:83
qw422016.N().S(`
<p class="error">`)
//line auth/auth.qtpl:84
qw422016.E().S(lc.Get("auth.error_password"))
//line auth/auth.qtpl:84
qw422016.N().S(`</p>
`)
//line auth/auth.qtpl:85
default:
//line auth/auth.qtpl:85
qw422016.N().S(`
<p class="error">`)
//line auth/auth.qtpl:86
qw422016.E().S(err)
//line auth/auth.qtpl:86
qw422016.N().S(`</p>
`)
//line auth/auth.qtpl:87
}
//line auth/auth.qtpl:87
qw422016.N().S(`
<p><a href="/login"> `)
//line auth/auth.qtpl:88
qw422016.E().S(lc.Get("auth.try_again"))
//line auth/auth.qtpl:88
qw422016.N().S(`</a></p>
</section>
</main>
`)
//line auth/auth.qtpl:91
}
//line auth/auth.qtpl:91
func WriteLoginError(qq422016 qtio422016.Writer, err string, lc *l18n.Localizer) {
//line auth/auth.qtpl:91
qw422016 := qt422016.AcquireWriter(qq422016)
//line auth/auth.qtpl:91
StreamLoginError(qw422016, err, lc)
//line auth/auth.qtpl:91
qt422016.ReleaseWriter(qw422016)
//line auth/auth.qtpl:91
}
//line auth/auth.qtpl:91
func LoginError(err string, lc *l18n.Localizer) string {
//line auth/auth.qtpl:91
qb422016 := qt422016.AcquireByteBuffer()
//line auth/auth.qtpl:91
WriteLoginError(qb422016, err, lc)
//line auth/auth.qtpl:91
qs422016 := string(qb422016.B)
//line auth/auth.qtpl:91
qt422016.ReleaseByteBuffer(qb422016)
//line auth/auth.qtpl:91
return qs422016
//line auth/auth.qtpl:91
}
//line auth/auth.qtpl:93
func StreamLogout(qw422016 *qt422016.Writer, can bool, lc *l18n.Localizer) {
//line auth/auth.qtpl:93
qw422016.N().S(`
<main class="main-width">
<section>
`)
//line auth/auth.qtpl:96
if can {
//line auth/auth.qtpl:96
qw422016.N().S(`
<h1>`)
//line auth/auth.qtpl:97
qw422016.E().S(lc.Get("auth.logout_header"))
//line auth/auth.qtpl:97
qw422016.N().S(`</h1>
<form method="POST" action="/logout">
<input class="btn btn_accent" type="submit" value="`)
//line auth/auth.qtpl:99
qw422016.E().S(lc.Get("auth.logout_button"))
//line auth/auth.qtpl:99
qw422016.N().S(`"/>
<a class="btn btn_weak" href="/">`)
//line auth/auth.qtpl:100
qw422016.E().S(lc.Get("auth.go_home"))
//line auth/auth.qtpl:100
qw422016.N().S(`</a>
</form>
`)
//line auth/auth.qtpl:102
} else {
//line auth/auth.qtpl:102
qw422016.N().S(`
<p>`)
//line auth/auth.qtpl:103
qw422016.E().S(lc.Get("auth.logout_anon"))
//line auth/auth.qtpl:103
qw422016.N().S(`</p>
<p><a href="/login">`)
//line auth/auth.qtpl:104
qw422016.E().S(lc.Get("auth.login_title"))
//line auth/auth.qtpl:104
qw422016.N().S(`</a></p>
<p><a href="/"> `)
//line auth/auth.qtpl:105
qw422016.E().S(lc.Get("auth.go_home"))
//line auth/auth.qtpl:105
qw422016.N().S(`</a></p>
`)
//line auth/auth.qtpl:106
}
//line auth/auth.qtpl:106
qw422016.N().S(`
</section>
</main>
`)
//line auth/auth.qtpl:109
}
//line auth/auth.qtpl:109
func WriteLogout(qq422016 qtio422016.Writer, can bool, lc *l18n.Localizer) {
//line auth/auth.qtpl:109
qw422016 := qt422016.AcquireWriter(qq422016)
//line auth/auth.qtpl:109
StreamLogout(qw422016, can, lc)
//line auth/auth.qtpl:109
qt422016.ReleaseWriter(qw422016)
//line auth/auth.qtpl:109
}
//line auth/auth.qtpl:109
func Logout(can bool, lc *l18n.Localizer) string {
//line auth/auth.qtpl:109
qb422016 := qt422016.AcquireByteBuffer()
//line auth/auth.qtpl:109
WriteLogout(qb422016, can, lc)
//line auth/auth.qtpl:109
qs422016 := string(qb422016.B)
//line auth/auth.qtpl:109
qt422016.ReleaseByteBuffer(qb422016)
//line auth/auth.qtpl:109
return qs422016
//line auth/auth.qtpl:109
}
//line auth/auth.qtpl:111
func StreamLock(qw422016 *qt422016.Writer, lc *l18n.Localizer) {
//line auth/auth.qtpl:111
qw422016.N().S(`
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>🔒 `)
//line auth/auth.qtpl:117
qw422016.E().S(lc.Get("auth.lock_title"))
//line auth/auth.qtpl:117
qw422016.N().S(`</title>
<link rel="shortcut icon" href="/static/favicon.ico">
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<main class="locked-notice main-width">
<section class="locked-notice__message">
<p class="locked-notice__lock">🔒</p>
<h1 class="locked-notice__title">`)
//line auth/auth.qtpl:125
qw422016.E().S(lc.Get("auth.lock_title"))
//line auth/auth.qtpl:125
qw422016.N().S(`</h1>
<form class="locked-notice__login-form" method="post" action="/login" id="login-form" enctype="multipart/form-data" autocomplete="on">
<label for="login-form__username">`)
//line auth/auth.qtpl:127
qw422016.E().S(lc.Get("auth.username"))
//line auth/auth.qtpl:127
qw422016.N().S(`</label>
<br>
<input type="text" required autofocus id="login-form__username" name="username" autocomplete="username">
<br>
<label for="login-form__password">`)
//line auth/auth.qtpl:131
qw422016.E().S(lc.Get("auth.password"))
//line auth/auth.qtpl:131
qw422016.N().S(`</label>
<br>
<input type="password" required name="password" autocomplete="current-password">
<br>
<button class="btn" type="submit" value="Log in">`)
//line auth/auth.qtpl:135
qw422016.E().S(lc.Get("auth.login_button"))
//line auth/auth.qtpl:135
qw422016.N().S(`</button>
</form>
`)
//line auth/auth.qtpl:137
streamtelegramWidget(qw422016, lc)
//line auth/auth.qtpl:137
qw422016.N().S(`
</section>
</main>
</body>
</html>
`)
//line auth/auth.qtpl:142
}
//line auth/auth.qtpl:142
func WriteLock(qq422016 qtio422016.Writer, lc *l18n.Localizer) {
//line auth/auth.qtpl:142
qw422016 := qt422016.AcquireWriter(qq422016)
//line auth/auth.qtpl:142
StreamLock(qw422016, lc)
//line auth/auth.qtpl:142
qt422016.ReleaseWriter(qw422016)
//line auth/auth.qtpl:142
}
//line auth/auth.qtpl:142
func Lock(lc *l18n.Localizer) string {
//line auth/auth.qtpl:142
qb422016 := qt422016.AcquireByteBuffer()
//line auth/auth.qtpl:142
WriteLock(qb422016, lc)
//line auth/auth.qtpl:142
qs422016 := string(qb422016.B)
//line auth/auth.qtpl:142
qt422016.ReleaseByteBuffer(qb422016)
//line auth/auth.qtpl:142
return qs422016
//line auth/auth.qtpl:142
}

View File

@ -79,6 +79,11 @@ func Register(username, password, group, source string, force bool) error {
return SaveUserDatabase()
}
var (
ErrUnknownUsername = errors.New("unknown username")
ErrWrongPassword = errors.New("wrong password")
)
// LoginDataHTTP logs such user in and returns string representation of an error if there is any.
//
// The HTTP parameters are used for setting header status (bad request, if it is bad) and saving a cookie.
@ -87,12 +92,12 @@ func LoginDataHTTP(w http.ResponseWriter, username, password string) error {
if !HasUsername(username) {
w.WriteHeader(http.StatusBadRequest)
log.Println("Unknown username", username, "was entered")
return errors.New("unknown username")
return ErrUnknownUsername
}
if !CredentialsOK(username, password) {
w.WriteHeader(http.StatusBadRequest)
log.Println("A wrong password was entered for username", username)
return errors.New("wrong password")
return ErrWrongPassword
}
token, err := AddSession(username)
if err != nil {

View File

@ -3,33 +3,29 @@
"password": "Password",
"register_title": "Register",
"register_header": "Register on {{.name}}",
"register_header": "",
"register_button": "Register",
"login_title": "Login",
"login_header": "Log in to {{.name}}",
"login_button": "Log in",
"logout_title": "Logout?",
"logout_title": "",
"logout_header": "Log out?",
"logout_button": "Confirm",
"logout_anon": "You cannot log out because you are not logged in.",
"logout_anon": "",
"lock_title": "Locked",
"password_tip": "The server stores your password in an encrypted form; even administrators cannot read it.",
"cookie_tip": "By submitting this form you give this wiki a permission to store cookies in your browser. It lets the engine associate your edits with you. You will stay logged in until you log out.",
"telegram_tip": "You can log in using Telegram. It only works if you have set your @username in Telegram and this username is free on this wiki.",
"password_tip": "",
"cookie_tip": "",
"telegram_tip": "",
"noauth": "Authentication is disabled. You can make edits anonymously.",
"noauth": "",
"noregister": "Registrations are currently closed. Administrators can make an account for you by hand; contact them.",
"error_username": "Unknown username.",
"error_password": "Wrong password.",
"error_telegram": "Could not authorize using Telegram.",
"error_username": "",
"error_password": "",
"error_telegram": "",
"go_back": "Go back",
"go_home": "Go home",
"go_home": "",
"go_login": "Go to the login page",
"try_again": "Try again"
}

View File

@ -2,31 +2,31 @@
"username": "Логин",
"password": "Пароль",
"register_title": "Регистрация",
"register_header": "Регистрация на «{{.name}}»",
"register_button": "Зарегистрироваться",
"register_title": "",
"register_header": "",
"register_button": "",
"login_title": "Вход",
"login_header": "Вход в «{{.name}}»",
"login_button": "Войти",
"login_header": "",
"login_button": "",
"logout_title": "Выйти?",
"logout_header": "Выйти?",
"logout_title": "",
"logout_header": "?",
"logout_button": "Подтвердить",
"logout_anon": "Вы не можете выйти, потому что ещё не вошли.",
"logout_anon": "",
"lock_title": "Доступ закрыт",
"lock_title": "",
"password_tip": "Сервер хранит ваш пароль в зашифрованном виде, даже администраторы не смогут его прочесть.",
"cookie_tip": "Отправляя эту форму, вы разрешаете вики хранить cookie в вашем браузере. Это позволит движку связывать ваши правки с вашей учётной записью. Вы будете авторизованы, пока не выйдете из учётной записи.",
"password_tip": "",
"cookie_tip": "",
"telegram_tip": "Вы можете войти с помощью Телеграм. Это сработает, если у вашего профиля есть @имя, и оно не занято в этой вики.",
"noauth": "Аутентификация отключена. Вы можете делать правки анонимно.",
"noauth": "",
"noregister": "Регистрация в текущее время недоступна. Администраторы могут вручную создать вам учётную запись, свяжитесь с ними.",
"error_username": "Неизвестное имя пользователя.",
"error_username": "",
"error_password": "Неверный пароль.",
"error_telegram": "Не удалось авторизоваться через Телеграм.",
"error_telegram": "",
"go_back": "Назад",
"go_home": "Домой",

View File

@ -2,7 +2,6 @@
//
//go:generate go run github.com/valyala/quicktemplate/qtc -dir=history
//go:generate go run github.com/valyala/quicktemplate/qtc -dir=mycoopts
//go:generate go run github.com/valyala/quicktemplate/qtc -dir=auth
package main
import (

View File

@ -2,16 +2,17 @@ package web
import (
"fmt"
"github.com/bouncepaw/mycorrhiza/internal/cfg"
user2 "github.com/bouncepaw/mycorrhiza/internal/user"
"github.com/bouncepaw/mycorrhiza/util"
viewutil2 "github.com/bouncepaw/mycorrhiza/web/viewutil"
"github.com/gorilla/mux"
"log"
"mime"
"net/http"
"os"
"sort"
"github.com/bouncepaw/mycorrhiza/internal/cfg"
"github.com/bouncepaw/mycorrhiza/internal/user"
"github.com/bouncepaw/mycorrhiza/util"
"github.com/bouncepaw/mycorrhiza/web/viewutil"
"github.com/gorilla/mux"
)
const adminTranslationRu = `
@ -52,53 +53,53 @@ const adminTranslationRu = `
{{define "delete user warning"}}Вы уверены, что хотите удалить этого пользователя из базы данных? Это действие нельзя отменить.{{end}}
`
func viewPanel(meta viewutil2.Meta) {
viewutil2.ExecutePage(meta, panelChain, &viewutil2.BaseData{})
func viewPanel(meta viewutil.Meta) {
viewutil.ExecutePage(meta, panelChain, &viewutil.BaseData{})
}
type listData struct {
*viewutil2.BaseData
*viewutil.BaseData
UserHypha string
Users []*user2.User
Users []*user.User
}
func viewList(meta viewutil2.Meta, users []*user2.User) {
viewutil2.ExecutePage(meta, listChain, listData{
BaseData: &viewutil2.BaseData{},
func viewList(meta viewutil.Meta, users []*user.User) {
viewutil.ExecutePage(meta, listChain, listData{
BaseData: &viewutil.BaseData{},
UserHypha: cfg.UserHypha,
Users: users,
})
}
type newUserData struct {
*viewutil2.BaseData
*viewutil.BaseData
Form util.FormData
}
func viewNewUser(meta viewutil2.Meta, form util.FormData) {
viewutil2.ExecutePage(meta, newUserChain, newUserData{
BaseData: &viewutil2.BaseData{},
func viewNewUser(meta viewutil.Meta, form util.FormData) {
viewutil.ExecutePage(meta, newUserChain, newUserData{
BaseData: &viewutil.BaseData{},
Form: form,
})
}
type editDeleteUserData struct {
*viewutil2.BaseData
*viewutil.BaseData
Form util.FormData
U *user2.User
U *user.User
}
func viewEditUser(meta viewutil2.Meta, form util.FormData, u *user2.User) {
viewutil2.ExecutePage(meta, editUserChain, editDeleteUserData{
BaseData: &viewutil2.BaseData{},
func viewEditUser(meta viewutil.Meta, form util.FormData, u *user.User) {
viewutil.ExecutePage(meta, editUserChain, editDeleteUserData{
BaseData: &viewutil.BaseData{},
Form: form,
U: u,
})
}
func viewDeleteUser(meta viewutil2.Meta, form util.FormData, u *user2.User) {
viewutil2.ExecutePage(meta, deleteUserChain, editDeleteUserData{
BaseData: &viewutil2.BaseData{},
func viewDeleteUser(meta viewutil.Meta, form util.FormData, u *user.User) {
viewutil.ExecutePage(meta, deleteUserChain, editDeleteUserData{
BaseData: &viewutil.BaseData{},
Form: form,
U: u,
})
@ -108,12 +109,12 @@ func viewDeleteUser(meta viewutil2.Meta, form util.FormData, u *user2.User) {
func handlerAdmin(w http.ResponseWriter, rq *http.Request) {
w.Header().Set("Content-Type", "text/html;charset=utf-8")
w.WriteHeader(http.StatusOK)
viewPanel(viewutil2.MetaFrom(w, rq))
viewPanel(viewutil.MetaFrom(w, rq))
}
// handlerAdminShutdown kills the wiki.
func handlerAdminShutdown(w http.ResponseWriter, rq *http.Request) {
if user2.CanProceed(rq, "admin/shutdown") {
if user.CanProceed(rq, "admin/shutdown") {
log.Println("An admin commanded the wiki to shutdown")
os.Exit(0)
}
@ -121,7 +122,7 @@ func handlerAdminShutdown(w http.ResponseWriter, rq *http.Request) {
// handlerAdminReindexUsers reinitialises the user system.
func handlerAdminReindexUsers(w http.ResponseWriter, rq *http.Request) {
user2.ReadUsersFromFilesystem()
user.ReadUsersFromFilesystem()
redirectTo := rq.Referer()
if redirectTo == "" {
redirectTo = "/hypha/" + cfg.UserHypha
@ -131,8 +132,8 @@ func handlerAdminReindexUsers(w http.ResponseWriter, rq *http.Request) {
func handlerAdminUsers(w http.ResponseWriter, rq *http.Request) {
// Get a sorted list of users
var users []*user2.User
for u := range user2.YieldUsers() {
var users []*user.User
for u := range user.YieldUsers() {
users = append(users, u)
}
@ -140,12 +141,12 @@ func handlerAdminUsers(w http.ResponseWriter, rq *http.Request) {
less := users[i].RegisteredAt.Before(users[j].RegisteredAt)
return less
})
viewList(viewutil2.MetaFrom(w, rq), users)
viewList(viewutil.MetaFrom(w, rq), users)
}
func handlerAdminUserEdit(w http.ResponseWriter, rq *http.Request) {
vars := mux.Vars(rq)
u := user2.ByName(vars["username"])
u := user.ByName(vars["username"])
if u == nil {
util.HTTP404Page(w, "404 page not found")
return
@ -157,9 +158,9 @@ func handlerAdminUserEdit(w http.ResponseWriter, rq *http.Request) {
oldGroup := u.Group
newGroup := f.Get("group")
if user2.ValidGroup(newGroup) {
if user.ValidGroup(newGroup) {
u.Group = newGroup
if err := user2.SaveUserDatabase(); err != nil {
if err := user.SaveUserDatabase(); err != nil {
u.Group = oldGroup
log.Println(err)
f = f.WithError(err)
@ -179,12 +180,12 @@ func handlerAdminUserEdit(w http.ResponseWriter, rq *http.Request) {
}
w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
viewEditUser(viewutil2.MetaFrom(w, rq), f, u)
viewEditUser(viewutil.MetaFrom(w, rq), f, u)
}
func handlerAdminUserChangePassword(w http.ResponseWriter, rq *http.Request) {
vars := mux.Vars(rq)
u := user2.ByName(vars["username"])
u := user.ByName(vars["username"])
if u == nil {
util.HTTP404Page(w, "404 page not found")
return
@ -204,7 +205,7 @@ func handlerAdminUserChangePassword(w http.ResponseWriter, rq *http.Request) {
if err := u.ChangePassword(password); err != nil {
f = f.WithError(err)
} else {
if err := user2.SaveUserDatabase(); err != nil {
if err := user.SaveUserDatabase(); err != nil {
u.Password = previousPassword
f = f.WithError(err)
} else {
@ -222,12 +223,12 @@ func handlerAdminUserChangePassword(w http.ResponseWriter, rq *http.Request) {
}
w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
viewEditUser(viewutil2.MetaFrom(w, rq), f, u)
viewEditUser(viewutil.MetaFrom(w, rq), f, u)
}
func handlerAdminUserDelete(w http.ResponseWriter, rq *http.Request) {
vars := mux.Vars(rq)
u := user2.ByName(vars["username"])
u := user.ByName(vars["username"])
if u == nil {
util.HTTP404Page(w, "404 page not found")
return
@ -236,7 +237,7 @@ func handlerAdminUserDelete(w http.ResponseWriter, rq *http.Request) {
f := util.NewFormData()
if rq.Method == http.MethodPost {
f = f.WithError(user2.DeleteUser(u.Name))
f = f.WithError(user.DeleteUser(u.Name))
if !f.HasError() {
http.Redirect(w, rq, "/admin/users/", http.StatusSeeOther)
} else {
@ -248,23 +249,23 @@ func handlerAdminUserDelete(w http.ResponseWriter, rq *http.Request) {
w.WriteHeader(http.StatusBadRequest)
}
w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
viewDeleteUser(viewutil2.MetaFrom(w, rq), f, u)
viewDeleteUser(viewutil.MetaFrom(w, rq), f, u)
}
func handlerAdminUserNew(w http.ResponseWriter, rq *http.Request) {
if rq.Method == http.MethodGet {
w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
viewNewUser(viewutil2.MetaFrom(w, rq), util.NewFormData())
viewNewUser(viewutil.MetaFrom(w, rq), util.NewFormData())
} else if rq.Method == http.MethodPost {
// Create a user
f := util.FormDataFromRequest(rq, []string{"name", "password", "group"})
err := user2.Register(f.Get("name"), f.Get("password"), f.Get("group"), "local", true)
err := user.Register(f.Get("name"), f.Get("password"), f.Get("group"), "local", true)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
viewNewUser(viewutil2.MetaFrom(w, rq), f.WithError(err))
viewNewUser(viewutil.MetaFrom(w, rq), f.WithError(err))
} else {
http.Redirect(w, rq, "/admin/users/", http.StatusSeeOther)
}

View File

@ -107,6 +107,7 @@ func (p *Page) RenderTo(meta viewutil.Meta, data map[string]any) error {
data["CommonScripts"] = cfg.CommonScripts
data["EditScripts"] = cfg.EditScripts
data["HeaderLinks"] = viewutil.HeaderLinks
data["UseAuth"] = cfg.UseAuth
tmpl := p.TemplateEnglish
if meta.LocaleIsRussian() {

View File

@ -12,6 +12,8 @@ var fs embed.FS
var pageOrphans, pageBacklinks, pageUserList, pageChangePassword *newtmpl.Page
var pageHyphaDelete, pageHyphaEdit, pageHyphaEmpty, pageHypha *newtmpl.Page
var pageRevision, pageMedia *newtmpl.Page
var pageAuthLock, pageAuthLogin, pageAuthLogout, pageAuthRegister, pageAuthTelegram *newtmpl.Page
var panelChain, listChain, newUserChain, editUserChain, deleteUserChain viewutil.Chain
func initPages() {
@ -136,4 +138,42 @@ func initPages() {
"remove tip": "Заметьте, чтобы заменить медиа, вам не нужно его перед этим откреплять.",
"remove btn": "Открепить",
}, "views/hypha-media.html")
pageAuthLock = newtmpl.NewPage(fs, map[string]string{
"lock title": "Доступ закрыт",
"username": "Логин",
"password": "Пароль",
"log in": "Войти",
}, "views/auth-telegram.html", "views/auth-lock.html")
pageAuthLogin = newtmpl.NewPage(fs, map[string]string{
"username": "Логин",
"password": "Пароль",
"log in": "Войти",
"cookie tip": "Отправляя эту форму, вы разрешаете вики хранить cookie в вашем браузере. Это позволит движку связывать ваши правки с вашей учётной записью. Вы будете авторизованы, пока не выйдете из учётной записи.",
"log in to x": "Войти в {{.}}",
"auth disabled": "Аутентификация отключена. Вы можете делать правки анонимно.",
"error username": "Неизвестное имя пользователя.",
"error password": "Неправильный пароль.",
"error telegram": "Не удалось войти через Телеграм.",
"go home": "Домой",
}, "views/auth-telegram.html", "views/auth-login.html")
pageAuthLogout = newtmpl.NewPage(fs, map[string]string{
"log out?": "Выйти?",
"log out": "Выйти",
"cannot log out anon": "Вы не можете выйти, потому что ещё не вошли.",
"log in": "Войти",
"go home": "Домой",
}, "views/auth-logout.html")
pageAuthRegister = newtmpl.NewPage(fs, map[string]string{
"username": "Логин",
"password": "Пароль",
"cookie tip": "Отправляя эту форму, вы разрешаете вики хранить cookie в вашем браузере. Это позволит движку связывать ваши правки с вашей учётной записью. Вы будете авторизованы, пока не выйдете из учётной записи.",
"password tip": "Сервер хранит ваш пароль в зашифрованном виде, даже администраторы не смогут его прочесть.",
"register btn": "Зарегистрироваться",
"register on x": "Регистрация на {{.}}",
}, "views/auth-telegram.html", "views/auth-register.html")
}

33
web/views/auth-lock.html Normal file
View File

@ -0,0 +1,33 @@
{{define "title"}}{{block "lock title" .}}Locked{{end}}{{end}}
{{define "page"}}
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>🔒 {{template "lock title" .}}</title>
<link rel="shortcut icon" href="/static/favicon.ico">
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<main class="locked-notice main-width">
<section class="locked-notice__message">
<p class="locked-notice__lock">🔒</p>
<h1 class="locked-notice__title">{{template "lock title" .}}</h1>
<form class="locked-notice__login-form" method="post" action="/login" id="login-form" enctype="multipart/form-data" autocomplete="on">
<label for="login-form__username">{{block "username" .}}Username{{end}}</label>
<br>
<input type="text" required autofocus id="login-form__username" name="username" autocomplete="username">
<br>
<label for="login-form__password">{{block "password" .}}Password{{end}}</label>
<br>
<input type="password" required name="password" autocomplete="current-password" id="login-form__password">
<br>
<button class="btn" type="submit" value="Log in">{{block "log in" .}}Log in{{end}}</button>
</form>
{{template "telegram widget"}}
</section>
</main>
</body>
</html>
{{end}}

39
web/views/auth-login.html Normal file
View File

@ -0,0 +1,39 @@
{{define "log in to x"}}Log in to {{.}}{{end}}
{{define "title"}}{{template "log in to x" .WikiName}}{{end}}
{{define "body"}}
<main class="main-width">
<section>
{{if .UseAuth}}
{{if .ErrUnknownUsername}}
<p class="error">{{block "error username" .}}Unknown username.{{end}}</p>
{{else if .ErrWrongPassword}}
<p class="error">{{block "error password" .}}Wrong password.{{end}}</p>
{{else if .ErrTelegram}}
<p class="error">{{block "error telegram" .}}Could not authorize using Telegram.{{end}}</p>
{{else if .Err}}
<p class="error">{{.Err}}</p>
{{end}}
<form class="modal" method="post" action="/login" id="login-form" enctype="multipart/form-data" autocomplete="on">
<fieldset class="modal__fieldset">
<legend class="modal__title">{{template "log in to x" .WikiName}}</legend>
<label for="login-form__username">{{block "username" .}}Username{{end}}</label>
<br>
<input type="text" required autofocus id="login-form__username" name="username" autocomplete="username"{{if .Username}} value="{{.Username}}"{{end}}>
<br>
<label for="login-form__password">{{block "password" .}}Password{{end}}</label>
<br>
<input type="password" required name="password" autocomplete="current-password">
<p>{{block "cookie tip" .}}By submitting this form you give this wiki a permission to store cookies in your browser. It lets the engine associate your edits with you. You will stay logged in until you log out.{{end}}</p>
<button class="btn" type="submit" value="Log in">{{block "log in" .}}Log in{{end}}</button>
<a class="btn btn_weak" href="/">{{block "cancel" .}}Cancel{{end}}</a>
</fieldset>
</form>
{{template "telegram widget" .}}
{{else}}
<p>{{block "auth disabled" .}}Authentication is disabled. You can make edits anonymously.{{end}}</p>
<p><a class="btn btn_weak" href="/">← {{block "go home" .}}Go home{{end}}</a></p>
{{end}}
</section>
</main>
{{end}}

View File

@ -0,0 +1,18 @@
{{define "title"}}{{end}}
{{define "body"}}
<main class="main-width">
<section>
{{if .CanLogout}}
<h1>{{block "log out?" .}}Log out?{{end}}</h1>
<form method="POST" action="/logout">
<input class="btn btn_accent" type="submit" value='{{block "log out" .}}Log out{{end}}'/>
<p><a class="btn btn_weak" href="/">← {{block "go home" .}}Go home{{end}}</a></p>
</form>
{{else}}
<p>{{block "cannot log out anon" .}}You cannot log out because you are not logged in.{{end}}</p>
<p><a href="/login">{{block "log in" .}}Log in{{end}}</a></p>
<p><a href="/">← {{template "go home"}}</a></p>
{{end}}
</section>
</main>
{{end}}

View File

@ -0,0 +1,35 @@
{{define "register on x"}}Register on {{.}}{{end}}
{{define "title"}}{{template "register on x" .WikiName}}{{end}}
{{define "body"}}
<main class="main-width">
<section>
{{if .AllowRegistration}}
<form class="modal" method="post" action="/register?{{.RawQuery}}" id="register-form" enctype="multipart/form-data" autocomplete="off">
<fieldset class="modal__fieldset">
<legend class="modal__title">{{template "register on x" .WikiName}}</legend>
{{if .Err}}<p class="error">{{.Err.Error}}</p>{{end}}
<label for="register-form__username">{{block "username" .}}Username{{end}}</label>
<br>
<input type="text" required autofocus id="register-form__username" name="username"{{if .Username}} value="{{.Username}}"{{end}}>
<br>
<label for="register-form__password">{{block "password" .}}Password{{end}}</label>
<br>
<input type="password" required name="password" id="register-form__password"{{if .Password}} value="{{.Password}}"{{end}}>
<p>{{block "password tip" .}}The server stores your password in an encrypted form; even administrators cannot read it.{{end}}</p>
<p>{{block "cookie tip" .}}By submitting this form you give this wiki a permission to store cookies in your browser. It lets the engine associate your edits with you. You will stay logged in until you log out.{{end}}</p>
<button class="btn" type="submit">{{block "register btn" .}}Register{{end}}</button>
<a class="btn btn_weak" href="/{{.RawQuery}}">{{block "cancel" .}}Cancel{{end}}</a>
</fieldset>
</form>
{{template "telegram widget" .}}
{{else if .UseAuth}}
<p>{%s lc.Get("auth.noregister") %}</p>
<p><a href="/{{.RawQuery}}">← {%s lc.Get("auth.go_back") %}</a></p>
{{else}}
<p>{%s lc.Get("auth.noauth") %}</p>
<p><a href="/{{.RawQuery}}">← {%s lc.Get("auth.go_back") %}</a></p>
{{end}}
</section>
</main>
{{end}}

View File

@ -0,0 +1,11 @@
{{define "telegram widget"}}
{{if .TelegramEnabled}}<!-- TODO: translate -->
<p class="telegram-notice">{{block "telegram tip" .}}You can log in using Telegram. It only works if you have set your @username in Telegram and this username is free on this wiki.{{end}}</p>
<script async
src="https://telegram.org/js/telegram-widget.js?15"
data-telegram-login="{{.TelegramBotName}}"
data-size="medium"
data-userpic="false"
data-auth-url="{{.URL}}/telegram-login"></script>
{{end}}
{{end}}

View File

@ -5,17 +5,17 @@ import (
"errors"
"fmt"
"github.com/bouncepaw/mycorrhiza/internal/cfg"
user2 "github.com/bouncepaw/mycorrhiza/internal/user"
"github.com/bouncepaw/mycorrhiza/internal/user"
"github.com/bouncepaw/mycorrhiza/l18n"
viewutil2 "github.com/bouncepaw/mycorrhiza/web/viewutil"
"github.com/bouncepaw/mycorrhiza/web/viewutil"
"io"
"log"
"log/slog"
"mime"
"net/http"
"net/url"
"strings"
"github.com/bouncepaw/mycorrhiza/auth"
"github.com/bouncepaw/mycorrhiza/categories"
"github.com/bouncepaw/mycorrhiza/help"
"github.com/bouncepaw/mycorrhiza/history/histweb"
@ -63,7 +63,7 @@ func Handler() http.Handler {
r := router.PathPrefix("").Subrouter()
r.Use(func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, rq *http.Request) {
user := user2.FromRequest(rq)
user := user.FromRequest(rq)
if !user.ShowLockMaybe(w, rq) {
next.ServeHTTP(w, rq)
}
@ -117,7 +117,7 @@ func Handler() http.Handler {
func groupMiddleware(group string) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, rq *http.Request) {
if cfg.UseAuth && user2.CanProceed(rq, group) {
if cfg.UseAuth && user.CanProceed(rq, group) {
next.ServeHTTP(w, rq)
return
}
@ -133,8 +133,8 @@ func groupMiddleware(group string) func(http.Handler) http.Handler {
// Auth
func handlerUserList(w http.ResponseWriter, rq *http.Request) {
admins, moderators, editors, readers := user2.UsersInGroups()
_ = pageUserList.RenderTo(viewutil2.MetaFrom(w, rq),
admins, moderators, editors, readers := user.UsersInGroups()
_ = pageUserList.RenderTo(viewutil.MetaFrom(w, rq),
map[string]any{
"Admins": admins,
"Moderators": moderators,
@ -144,53 +144,47 @@ func handlerUserList(w http.ResponseWriter, rq *http.Request) {
}
func handlerLock(w http.ResponseWriter, rq *http.Request) {
_, _ = io.WriteString(w, auth.Lock(l18n.FromRequest(rq)))
_ = pageAuthLock.RenderTo(viewutil.MetaFrom(w, rq), map[string]any{})
}
// handlerRegister displays the register form (GET) or registers the user (POST).
func handlerRegister(w http.ResponseWriter, rq *http.Request) {
lc := l18n.FromRequest(rq)
util.PrepareRq(rq)
if rq.Method == http.MethodGet {
_, _ = io.WriteString(
w,
viewutil2.Base(
viewutil2.MetaFrom(w, rq),
lc.Get("auth.register_title"),
auth.Register(rq),
map[string]string{},
),
)
slog.Info("Showing registration form")
_ = pageAuthRegister.RenderTo(viewutil.MetaFrom(w, rq), map[string]any{
"UseAuth": cfg.UseAuth,
"AllowRegistration": cfg.AllowRegistration,
"RawQuery": rq.URL.RawQuery,
"WikiName": cfg.WikiName,
})
return
}
var (
username = rq.PostFormValue("username")
password = rq.PostFormValue("password")
err = user2.Register(username, password, "editor", "local", false)
err = user.Register(username, password, "editor", "local", false)
)
if err != nil {
log.Printf("Failed to register %s: %s", username, err.Error())
slog.Info("Failed to register", "username", username, "err", err.Error())
w.Header().Set("Content-Type", mime.TypeByExtension(".html"))
w.WriteHeader(http.StatusBadRequest)
_, _ = io.WriteString(
w,
viewutil2.Base(
viewutil2.MetaFrom(w, rq),
lc.Get("auth.register_title"),
fmt.Sprintf(
`<main class="main-width"><p>%s</p><p><a href="/register">%s<a></p></main>`,
err.Error(),
lc.Get("auth.try_again"),
),
map[string]string{},
),
)
_ = pageAuthRegister.RenderTo(viewutil.MetaFrom(w, rq), map[string]any{
"UseAuth": cfg.UseAuth,
"AllowRegistration": cfg.AllowRegistration,
"RawQuery": rq.URL.RawQuery,
"WikiName": cfg.WikiName,
"Err": err,
"Username": username,
"Password": password,
})
return
}
log.Printf("Successfully registered %s", username)
if err := user2.LoginDataHTTP(w, username, password); err != nil {
slog.Info("Registered user", "username", username)
if err := user.LoginDataHTTP(w, username, password); err != nil {
return
}
http.Redirect(w, rq, "/"+rq.URL.RawQuery, http.StatusSeeOther)
@ -198,71 +192,79 @@ func handlerRegister(w http.ResponseWriter, rq *http.Request) {
// handlerLogout shows the logout form (GET) or logs the user out (POST).
func handlerLogout(w http.ResponseWriter, rq *http.Request) {
if rq.Method == http.MethodGet {
var (
u = user2.FromRequest(rq)
can = u != nil
lc = l18n.FromRequest(rq)
)
w.Header().Set("Content-Type", "text/html;charset=utf-8")
if can {
log.Println("User", u.Name, "tries to log out")
w.WriteHeader(http.StatusOK)
} else {
log.Println("Unknown user tries to log out")
w.WriteHeader(http.StatusForbidden)
}
_, _ = io.WriteString(
w,
viewutil2.Base(viewutil2.MetaFrom(w, rq), lc.Get("auth.logout_title"), auth.Logout(can, lc), map[string]string{}),
)
} else if rq.Method == http.MethodPost {
user2.LogoutFromRequest(w, rq)
if rq.Method == http.MethodPost {
slog.Info("Somebody logged out")
user.LogoutFromRequest(w, rq)
http.Redirect(w, rq, "/", http.StatusSeeOther)
return
}
var (
u = user.FromRequest(rq)
can = u != nil
)
w.Header().Set("Content-Type", "text/html;charset=utf-8")
if can {
slog.Info("Logging out", "username", u.Name)
w.WriteHeader(http.StatusOK)
} else {
slog.Info("Unknown user logging out")
w.WriteHeader(http.StatusForbidden)
}
_ = pageAuthLogout.RenderTo(viewutil.MetaFrom(w, rq), map[string]any{
"CanLogout": can,
})
}
// handlerLogin shows the login form (GET) or logs the user in (POST).
func handlerLogin(w http.ResponseWriter, rq *http.Request) {
lc := l18n.FromRequest(rq)
if rq.Method == http.MethodGet {
w.Header().Set("Content-Type", "text/html;charset=utf-8")
w.WriteHeader(http.StatusOK)
_, _ = io.WriteString(
w,
viewutil2.Base(
viewutil2.MetaFrom(w, rq),
lc.Get("auth.login_title"),
auth.Login(lc),
map[string]string{},
),
)
} else if rq.Method == http.MethodPost {
var (
username = util.CanonicalName(rq.PostFormValue("username"))
password = rq.PostFormValue("password")
err = user2.LoginDataHTTP(w, username, password)
)
if err != nil {
w.Header().Set("Content-Type", "text/html;charset=utf-8")
w.WriteHeader(http.StatusInternalServerError)
_, _ = io.WriteString(w, viewutil2.Base(viewutil2.MetaFrom(w, rq), err.Error(), auth.LoginError(err.Error(), lc), map[string]string{}))
return
}
http.Redirect(w, rq, "/", http.StatusSeeOther)
_ = pageAuthLogin.RenderTo(viewutil.MetaFrom(w, rq), map[string]any{
"UseAuth": cfg.UseAuth,
"ErrUnknownUsername": false,
"ErrWrongPassword": false,
"ErrTelegram": false,
"Err": nil,
"WikiName": cfg.WikiName,
})
slog.Info("Somebody logging in")
return
}
var (
username = util.CanonicalName(rq.PostFormValue("username"))
password = rq.PostFormValue("password")
err = user.LoginDataHTTP(w, username, password)
)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
_ = pageAuthLogin.RenderTo(viewutil.MetaFrom(w, rq), map[string]any{
"UseAuth": cfg.UseAuth,
"ErrUnknownUsername": errors.Is(err, user.ErrUnknownUsername),
"ErrWrongPassword": errors.Is(err, user.ErrWrongPassword),
"ErrTelegram": false, // TODO: ?
"Err": err.Error(),
"WikiName": cfg.WikiName,
"Username": username,
})
slog.Info("Failed to log in", "username", username, "err", err.Error())
return
}
http.Redirect(w, rq, "/", http.StatusSeeOther)
slog.Info("Logged in", "username", username)
}
func handlerTelegramLogin(w http.ResponseWriter, rq *http.Request) {
// Note there is no lock here.
lc := l18n.FromRequest(rq)
w.Header().Set("Content-Type", "text/html;charset=utf-8")
rq.ParseForm()
_ = rq.ParseForm()
var (
values = rq.URL.Query()
username = strings.ToLower(values.Get("username"))
seemsValid = user2.TelegramAuthParamsAreValid(values)
err = user2.Register(
seemsValid = user.TelegramAuthParamsAreValid(values)
err = user.Register(
username,
"", // Password matters not
"editor",
@ -272,7 +274,7 @@ func handlerTelegramLogin(w http.ResponseWriter, rq *http.Request) {
)
// If registering a user via Telegram failed, because a Telegram user with this name
// has already registered, then everything is actually ok!
if user2.HasUsername(username) && user2.ByName(username).Source == "telegram" {
if user.HasUsername(username) && user.ByName(username).Source == "telegram" {
err = nil
}
@ -281,12 +283,12 @@ func handlerTelegramLogin(w http.ResponseWriter, rq *http.Request) {
}
if err != nil {
log.Printf("Failed to register %s using Telegram: %s", username, err.Error())
slog.Info("Failed to register", "username", username, "err", err.Error(), "method", "telegram")
w.WriteHeader(http.StatusBadRequest)
_, _ = io.WriteString(
w,
viewutil2.Base(
viewutil2.MetaFrom(w, rq),
viewutil.Base(
viewutil.MetaFrom(w, rq),
lc.Get("ui.error"),
fmt.Sprintf(
`<main class="main-width"><p>%s</p><p>%s</p><p><a href="/login">%s<a></p></main>`,
@ -300,14 +302,14 @@ func handlerTelegramLogin(w http.ResponseWriter, rq *http.Request) {
return
}
errmsg := user2.LoginDataHTTP(w, username, "")
errmsg := user.LoginDataHTTP(w, username, "")
if errmsg != nil {
log.Printf("Failed to login %s using Telegram: %s", username, err.Error())
w.WriteHeader(http.StatusBadRequest)
_, _ = io.WriteString(
w,
viewutil2.Base(
viewutil2.MetaFrom(w, rq),
viewutil.Base(
viewutil.MetaFrom(w, rq),
"Error",
fmt.Sprintf(
`<main class="main-width"><p>%s</p><p>%s</p><p><a href="/login">%s<a></p></main>`,
@ -320,6 +322,6 @@ func handlerTelegramLogin(w http.ResponseWriter, rq *http.Request) {
)
return
}
log.Printf("Authorize %s from Telegram", username)
http.Redirect(w, rq, "/", http.StatusSeeOther)
slog.Info("Logged in", "username", username, "method", "telegram")
}