Update auth flows
This commit is contained in:
parent
4f1989f663
commit
a0cc2f5c66
|
@ -3246,6 +3246,12 @@ code, .code {
|
||||||
.w6 {
|
.w6 {
|
||||||
width: 32rem; }
|
width: 32rem; }
|
||||||
|
|
||||||
|
.w7 {
|
||||||
|
width: 48rem; }
|
||||||
|
|
||||||
|
.w8 {
|
||||||
|
width: 64rem; }
|
||||||
|
|
||||||
.w-10 {
|
.w-10 {
|
||||||
width: 10%; }
|
width: 10%; }
|
||||||
|
|
||||||
|
@ -3310,6 +3316,10 @@ code, .code {
|
||||||
width: 16rem; }
|
width: 16rem; }
|
||||||
.w6-ns, .edit-form textarea {
|
.w6-ns, .edit-form textarea {
|
||||||
width: 32rem; }
|
width: 32rem; }
|
||||||
|
.w7-ns {
|
||||||
|
width: 48rem; }
|
||||||
|
.w8-ns {
|
||||||
|
width: 64rem; }
|
||||||
.w-10-ns {
|
.w-10-ns {
|
||||||
width: 10%; }
|
width: 10%; }
|
||||||
.w-20-ns {
|
.w-20-ns {
|
||||||
|
@ -3358,6 +3368,10 @@ code, .code {
|
||||||
width: 16rem; }
|
width: 16rem; }
|
||||||
.w6-m {
|
.w6-m {
|
||||||
width: 32rem; }
|
width: 32rem; }
|
||||||
|
.w7-m {
|
||||||
|
width: 48rem; }
|
||||||
|
.w8-m {
|
||||||
|
width: 64rem; }
|
||||||
.w-10-m {
|
.w-10-m {
|
||||||
width: 10%; }
|
width: 10%; }
|
||||||
.w-20-m {
|
.w-20-m {
|
||||||
|
@ -3406,6 +3420,10 @@ code, .code {
|
||||||
width: 16rem; }
|
width: 16rem; }
|
||||||
.w6-l {
|
.w6-l {
|
||||||
width: 32rem; }
|
width: 32rem; }
|
||||||
|
.w7-l {
|
||||||
|
width: 48rem; }
|
||||||
|
.w8-l {
|
||||||
|
width: 64rem; }
|
||||||
.w-10-l {
|
.w-10-l {
|
||||||
width: 10%; }
|
width: 10%; }
|
||||||
.w-20-l {
|
.w-20-l {
|
||||||
|
@ -8422,10 +8440,10 @@ input[type=submit] {
|
||||||
input[type=submit]:hover {
|
input[type=submit]:hover {
|
||||||
color: #4c9ed9;
|
color: #4c9ed9;
|
||||||
color: var(--form-button-color-active);
|
color: var(--form-button-color-active);
|
||||||
background-color: #aaa;
|
background-color: #666;
|
||||||
background-color: var(--theme-color-dim);
|
background-color: var(--theme-color-light);
|
||||||
border-color: #aaa;
|
border-color: #666;
|
||||||
border-color: var(--theme-color-dim); }
|
border-color: var(--theme-color-light); }
|
||||||
|
|
||||||
button.lite,
|
button.lite,
|
||||||
.button.lite,
|
.button.lite,
|
||||||
|
|
|
@ -216,6 +216,8 @@ pre, code, .codeblock {
|
||||||
--theme-color-dim: #444;
|
--theme-color-dim: #444;
|
||||||
--theme-color-dimmer: #383838;
|
--theme-color-dimmer: #383838;
|
||||||
--theme-color-dimmest: #333;
|
--theme-color-dimmest: #333;
|
||||||
|
--theme-color-dark: #666;
|
||||||
|
--theme-color-light: #666;
|
||||||
--link-color: #aaa;
|
--link-color: #aaa;
|
||||||
--link-border-color: #aaa;
|
--link-border-color: #aaa;
|
||||||
--hr-color: #aaa;
|
--hr-color: #aaa;
|
||||||
|
|
|
@ -234,6 +234,8 @@ pre, code, .codeblock {
|
||||||
--theme-color-dim: #aaa;
|
--theme-color-dim: #aaa;
|
||||||
--theme-color-dimmer: #bbb;
|
--theme-color-dimmer: #bbb;
|
||||||
--theme-color-dimmest: #ccc;
|
--theme-color-dimmest: #ccc;
|
||||||
|
--theme-color-dark: #666;
|
||||||
|
--theme-color-light: #666;
|
||||||
--link-color: #666;
|
--link-color: #666;
|
||||||
--link-border-color: #666;
|
--link-border-color: #666;
|
||||||
--hr-color: #444;
|
--hr-color: #444;
|
||||||
|
|
|
@ -286,7 +286,7 @@ func init() {
|
||||||
var err error
|
var err error
|
||||||
switch emailType {
|
switch emailType {
|
||||||
case "registration":
|
case "registration":
|
||||||
err = email.SendRegistrationEmail(toAddress, toName, "test_user", "test_token", p)
|
err = email.SendRegistrationEmail(toAddress, toName, "test_user", "test_token", "", p)
|
||||||
case "passwordreset":
|
case "passwordreset":
|
||||||
err = email.SendPasswordReset(toAddress, toName, "test_user", "test_token", time.Now().Add(time.Hour*24), p)
|
err = email.SendPasswordReset(toAddress, toName, "test_user", "test_token", time.Now().Add(time.Hour*24), p)
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -26,14 +26,21 @@ type RegistrationEmailData struct {
|
||||||
CompleteRegistrationUrl string
|
CompleteRegistrationUrl string
|
||||||
}
|
}
|
||||||
|
|
||||||
func SendRegistrationEmail(toAddress string, toName string, username string, completionToken string, perf *perf.RequestPerf) error {
|
func SendRegistrationEmail(
|
||||||
|
toAddress string,
|
||||||
|
toName string,
|
||||||
|
username string,
|
||||||
|
completionToken string,
|
||||||
|
destination string,
|
||||||
|
perf *perf.RequestPerf,
|
||||||
|
) error {
|
||||||
perf.StartBlock("EMAIL", "Registration email")
|
perf.StartBlock("EMAIL", "Registration email")
|
||||||
|
|
||||||
perf.StartBlock("EMAIL", "Rendering template")
|
perf.StartBlock("EMAIL", "Rendering template")
|
||||||
contents, err := renderTemplate("email_registration.html", RegistrationEmailData{
|
contents, err := renderTemplate("email_registration.html", RegistrationEmailData{
|
||||||
Name: toName,
|
Name: toName,
|
||||||
HomepageUrl: hmnurl.BuildHomepage(),
|
HomepageUrl: hmnurl.BuildHomepage(),
|
||||||
CompleteRegistrationUrl: hmnurl.BuildEmailConfirmation(username, completionToken),
|
CompleteRegistrationUrl: hmnurl.BuildEmailConfirmation(username, completionToken, destination),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -28,7 +28,7 @@ var WRJ2021 = Jam{
|
||||||
var WRJ2022 = Jam{
|
var WRJ2022 = Jam{
|
||||||
Name: "Wheel Reinvention Jam 2022",
|
Name: "Wheel Reinvention Jam 2022",
|
||||||
Slug: "WRJ2022",
|
Slug: "WRJ2022",
|
||||||
StartTime: time.Date(2022, 8, 15, 8, 0, 0, 0, utils.Must1(time.LoadLocation("America/Los_Angeles"))),
|
StartTime: time.Date(2022, 8, 3, 8, 0, 0, 0, utils.Must1(time.LoadLocation("America/Los_Angeles"))),
|
||||||
EndTime: time.Date(2022, 8, 22, 8, 0, 0, 0, utils.Must1(time.LoadLocation("America/Los_Angeles"))),
|
EndTime: time.Date(2022, 8, 22, 8, 0, 0, 0, utils.Must1(time.LoadLocation("America/Los_Angeles"))),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -119,9 +119,13 @@ func BuildRegistrationSuccess() string {
|
||||||
|
|
||||||
var RegexEmailConfirmation = regexp.MustCompile("^/email_confirmation/(?P<username>[^/]+)/(?P<token>[^/]+)$")
|
var RegexEmailConfirmation = regexp.MustCompile("^/email_confirmation/(?P<username>[^/]+)/(?P<token>[^/]+)$")
|
||||||
|
|
||||||
func BuildEmailConfirmation(username, token string) string {
|
func BuildEmailConfirmation(username, token string, destination string) string {
|
||||||
defer CatchPanic()
|
defer CatchPanic()
|
||||||
return Url(fmt.Sprintf("/email_confirmation/%s/%s", url.PathEscape(username), token), nil)
|
var query []Q
|
||||||
|
if destination != "" {
|
||||||
|
query = append(query, Q{"destination", destination})
|
||||||
|
}
|
||||||
|
return Url(fmt.Sprintf("/email_confirmation/%s/%s", url.PathEscape(username), token), query)
|
||||||
}
|
}
|
||||||
|
|
||||||
var RegexRequestPasswordReset = regexp.MustCompile("^/password_reset$")
|
var RegexRequestPasswordReset = regexp.MustCompile("^/password_reset$")
|
||||||
|
|
|
@ -163,8 +163,8 @@ input, select, textarea {
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@include usevar('color', 'form-button-color-active');
|
@include usevar('color', 'form-button-color-active');
|
||||||
@include usevar('background-color', 'theme-color-dim');
|
@include usevar('background-color', 'theme-color-light');
|
||||||
@include usevar('border-color', 'theme-color-dim');
|
@include usevar('border-color', 'theme-color-light');
|
||||||
}
|
}
|
||||||
|
|
||||||
&.lite {
|
&.lite {
|
||||||
|
|
|
@ -43,6 +43,8 @@ $width-3: 4rem !default;
|
||||||
$width-4: 8rem !default;
|
$width-4: 8rem !default;
|
||||||
$width-5: 16rem !default;
|
$width-5: 16rem !default;
|
||||||
$width-6: 32rem !default;
|
$width-6: 32rem !default;
|
||||||
|
$width-7: 48rem !default;
|
||||||
|
$width-8: 64rem !default;
|
||||||
$max-width-1: 1rem !default;
|
$max-width-1: 1rem !default;
|
||||||
$max-width-2: 2rem !default;
|
$max-width-2: 2rem !default;
|
||||||
$max-width-3: 4rem !default;
|
$max-width-3: 4rem !default;
|
||||||
|
|
|
@ -55,6 +55,8 @@
|
||||||
.w4 { width: $width-4; }
|
.w4 { width: $width-4; }
|
||||||
.w5 { width: $width-5; }
|
.w5 { width: $width-5; }
|
||||||
.w6 { width: $width-6; }
|
.w6 { width: $width-6; }
|
||||||
|
.w7 { width: $width-7; }
|
||||||
|
.w8 { width: $width-8; }
|
||||||
|
|
||||||
.w-10 { width: 10%; }
|
.w-10 { width: 10%; }
|
||||||
.w-20 { width: 20%; }
|
.w-20 { width: 20%; }
|
||||||
|
@ -82,6 +84,8 @@
|
||||||
.w4-ns { width: $width-4; }
|
.w4-ns { width: $width-4; }
|
||||||
.w5-ns { width: $width-5; }
|
.w5-ns { width: $width-5; }
|
||||||
.w6-ns { width: $width-6; }
|
.w6-ns { width: $width-6; }
|
||||||
|
.w7-ns { width: $width-7; }
|
||||||
|
.w8-ns { width: $width-8; }
|
||||||
.w-10-ns { width: 10%; }
|
.w-10-ns { width: 10%; }
|
||||||
.w-20-ns { width: 20%; }
|
.w-20-ns { width: 20%; }
|
||||||
.w-25-ns { width: 25%; }
|
.w-25-ns { width: 25%; }
|
||||||
|
@ -108,6 +112,8 @@
|
||||||
.w4-m { width: $width-4; }
|
.w4-m { width: $width-4; }
|
||||||
.w5-m { width: $width-5; }
|
.w5-m { width: $width-5; }
|
||||||
.w6-m { width: $width-6; }
|
.w6-m { width: $width-6; }
|
||||||
|
.w7-m { width: $width-7; }
|
||||||
|
.w8-m { width: $width-8; }
|
||||||
.w-10-m { width: 10%; }
|
.w-10-m { width: 10%; }
|
||||||
.w-20-m { width: 20%; }
|
.w-20-m { width: 20%; }
|
||||||
.w-25-m { width: 25%; }
|
.w-25-m { width: 25%; }
|
||||||
|
@ -134,6 +140,8 @@
|
||||||
.w4-l { width: $width-4; }
|
.w4-l { width: $width-4; }
|
||||||
.w5-l { width: $width-5; }
|
.w5-l { width: $width-5; }
|
||||||
.w6-l { width: $width-6; }
|
.w6-l { width: $width-6; }
|
||||||
|
.w7-l { width: $width-7; }
|
||||||
|
.w8-l { width: $width-8; }
|
||||||
.w-10-l { width: 10%; }
|
.w-10-l { width: 10%; }
|
||||||
.w-20-l { width: 20%; }
|
.w-20-l { width: 20%; }
|
||||||
.w-25-l { width: 25%; }
|
.w-25-l { width: 25%; }
|
||||||
|
|
|
@ -9,6 +9,9 @@ $vars: (
|
||||||
theme-color-dimmer: #383838,
|
theme-color-dimmer: #383838,
|
||||||
theme-color-dimmest: #333,
|
theme-color-dimmest: #333,
|
||||||
|
|
||||||
|
theme-color-dark: #666,
|
||||||
|
theme-color-light: #666,
|
||||||
|
|
||||||
link-color: #aaa,
|
link-color: #aaa,
|
||||||
link-border-color: #aaa,
|
link-border-color: #aaa,
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,9 @@ $vars: (
|
||||||
theme-color-dimmer: #bbb,
|
theme-color-dimmer: #bbb,
|
||||||
theme-color-dimmest: #ccc,
|
theme-color-dimmest: #ccc,
|
||||||
|
|
||||||
|
theme-color-dark: #666,
|
||||||
|
theme-color-light: #666,
|
||||||
|
|
||||||
link-color: #666,
|
link-color: #666,
|
||||||
link-border-color: #666,
|
link-border-color: #666,
|
||||||
|
|
||||||
|
|
|
@ -1,41 +1,38 @@
|
||||||
{{ template "base.html" . }}
|
{{ template "base.html" . }}
|
||||||
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<div class="content-block">
|
<div class="flex ph3 ph0-ns justify-center">
|
||||||
<form id="password_reset_form" method="post">
|
<div class="w-100 w-auto-ns pv3">
|
||||||
<input type="hidden" name="token" value="{{ .Token }}" />
|
|
||||||
{{/*NOTE: The username field isn't `type="hidden"` because this way browser will offer to save the username along with the password */}}
|
|
||||||
<input style="position:absolute; visibility:hidden;" type="text" name="username" value="{{ .Username }}" readonly />
|
|
||||||
|
|
||||||
<h1>Hi, {{ .Username }}!</h1>
|
<h1>Hi, {{ .Username }}!</h1>
|
||||||
<p class="mb3 b">Please enter a new password</p>
|
<form method="post" class="flex flex-column">
|
||||||
<p class="mb2">
|
<input type="hidden" name="token" value="{{ .Token }}" />
|
||||||
<label class="db b" for="password">New Password</label>
|
|
||||||
<input class="db" type="password" name="password" minlength="8" required />
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p class="mb2">
|
{{/*NOTE: The username field isn't `type="hidden"` because this way browser will offer to save the username along with the password */}}
|
||||||
<label class="db b" for="password2">New password confirmation</label>
|
<input
|
||||||
<input class="db" type="password" name="password2" minlength="8" required />
|
style="position:absolute; visibility:hidden;"
|
||||||
<span class="note db">Enter the same password as before, for verification.</span>
|
type="text"
|
||||||
</p>
|
name="username"
|
||||||
|
value="{{ .Username }}"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
|
||||||
<input class="db mt3" type="submit" value="Reset your password" />
|
<div>Please enter a new password.</div>
|
||||||
|
|
||||||
|
<div class="mt2">
|
||||||
|
<label class="db b" for="password">New password</label>
|
||||||
|
<input class="db w-100 w5-ns"
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
minlength="8"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input class="db mt3 w-100"
|
||||||
|
type="submit"
|
||||||
|
value="Reset your password"
|
||||||
|
/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
</div>
|
||||||
let form = document.querySelector("#password_reset_form")
|
|
||||||
let password1 = form.querySelector("input[name=password]")
|
|
||||||
let password2 = form.querySelector("input[name=password2]")
|
|
||||||
password1.addEventListener("input", validatePasswordConfirmation);
|
|
||||||
password2.addEventListener("input", validatePasswordConfirmation);
|
|
||||||
|
|
||||||
function validatePasswordConfirmation(ev) {
|
|
||||||
if (password1.value != password2.value) {
|
|
||||||
password2.setCustomValidity("Password doesn't match");
|
|
||||||
} else {
|
|
||||||
password2.setCustomValidity("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
@ -1,19 +1,39 @@
|
||||||
{{ template "base.html" . }}
|
{{ template "base.html" . }}
|
||||||
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<div class="content-block">
|
<div class="mw7 ph3 ph0-ns pb3">
|
||||||
<form method="POST">
|
<form method="POST">
|
||||||
<input type="hidden" name="token" value="{{ .Token }}" />
|
<input type="hidden" name="token" value="{{ .Token }}" />
|
||||||
|
<input type="hidden" name="destination" value="{{ .DestinationURL }}" />
|
||||||
|
|
||||||
{{/*NOTE: The username field isn't `type="hidden"` because this way browser will offer to save the username along with the password */}}
|
{{/*NOTE: The username field isn't `type="hidden"` because this way browser will offer to save the username along with the password */}}
|
||||||
<input style="position:absolute; visibility:hidden;" type="text" name="username" value="{{ .Username }}" readonly />
|
<input
|
||||||
|
style="position:absolute; visibility:hidden;"
|
||||||
|
name="username"
|
||||||
|
type="text"
|
||||||
|
value="{{ .Username }}"
|
||||||
|
readonly
|
||||||
|
/>
|
||||||
|
|
||||||
<h1>Hi, {{ .Username }}!</h1>
|
<h1>Hi, {{ .Username }}!</h1>
|
||||||
<p class="mb3 b">You're almost done signing up.</p>
|
<p class="mb3 b">
|
||||||
<p>To complete your registration and log in, please enter the password you used during the registration process.</p>
|
You're almost done signing up.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
To complete your registration and log in, please enter the password you used during the registration process.
|
||||||
|
</p>
|
||||||
<label class="db b" for="password">Password</label>
|
<label class="db b" for="password">Password</label>
|
||||||
<input class="db" type="password" name="password" minlength="8" required />
|
<input class="db w-100 w-auto-ns"
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
minlength="8"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
|
||||||
<input class="db mt3" type="submit" value="Complete registration" />
|
<input class="db mt3 w-100 w-auto-ns"
|
||||||
|
type="submit"
|
||||||
|
value="Complete registration"
|
||||||
|
/>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
@ -1,25 +1,43 @@
|
||||||
{{ template "base.html" . }}
|
{{ template "base.html" . }}
|
||||||
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<div class="content-block">
|
<div class="flex ph3 ph0-ns justify-center">
|
||||||
<h1>Please enter your username and password</h1>
|
<div class="w-100 w-auto-ns pv3">
|
||||||
<form method="POST">
|
<h1 class="tc">Log in</h1>
|
||||||
|
<form method="POST" class="flex flex-column">
|
||||||
<input type="hidden" name="redirect" value="{{ .RedirectUrl }}" />
|
<input type="hidden" name="redirect" value="{{ .RedirectUrl }}" />
|
||||||
<p class="mb2">
|
|
||||||
|
<div>
|
||||||
<label class="db b" for="username">Username</label>
|
<label class="db b" for="username">Username</label>
|
||||||
<input class="db w5" name="username" minlength="3" maxlength="30" type="text" required />
|
<input class="db w-100 w5-ns"
|
||||||
</p>
|
name="username"
|
||||||
|
type="text"
|
||||||
|
minlength="3" maxlength="30"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p class="mb2">
|
<div class="mt2">
|
||||||
|
<div class="flex justify-between">
|
||||||
<label class="db b" for="password">Password</label>
|
<label class="db b" for="password">Password</label>
|
||||||
<input class="db w5" name="password" minlength="8" type="password" required />
|
<a href="{{ .ForgotPasswordUrl }}" tabindex="-1">Forgot your password?</a>
|
||||||
</p>
|
</div>
|
||||||
|
<input class="db w-100 w5-ns"
|
||||||
|
name="password"
|
||||||
|
type="password"
|
||||||
|
minlength="8"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<a href="{{ .ForgotPasswordUrl }}">Forgot your password?</a>
|
<div class="mt3">
|
||||||
|
<input class="w-100" type="submit" value="Log in" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<p class="mt3">
|
<div class="tc pa3">
|
||||||
<input type="submit" value="Log in" />
|
Need an account? <a href="{{ .RegisterUrl }}">Sign up.</a>
|
||||||
</p>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
@ -1,26 +1,40 @@
|
||||||
{{ template "base.html" . }}
|
{{ template "base.html" . }}
|
||||||
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<div class="content-block">
|
<div class="flex ph3 ph0-ns justify-center">
|
||||||
<h1>Request a password reset link</h1>
|
<div class="w-100 w-auto-ns pv3">
|
||||||
<form method="POST">
|
<h1 class="tc">Reset your password</h1>
|
||||||
<p class="mb2">
|
<form method="POST" class="flex flex-column">
|
||||||
|
<div>
|
||||||
<label class="db b" for="username">Username</label>
|
<label class="db b" for="username">Username</label>
|
||||||
<input class="db w5" name="username" minlength="3" maxlength="30" type="text" required pattern="^[0-9a-zA-Z][\w-]{2,29}$" />
|
<input class="db w-100 w5-ns"
|
||||||
</p>
|
name="username"
|
||||||
|
type="text"
|
||||||
|
minlength="3" maxlength="30"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p class="mb2">
|
<div class="mt2">
|
||||||
<label class="db b" for="email">Email</label>
|
<label class="db b" for="email">Email</label>
|
||||||
<input class="db w5" name="email" type="text" required />
|
<input class="db w-100 w5-ns"
|
||||||
</p>
|
name="email"
|
||||||
|
type="text"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p class="mt3">
|
<div class="mt3">
|
||||||
<input type="submit" value="Request password reset link" />
|
<input class="w-100"
|
||||||
</p>
|
type="submit"
|
||||||
|
value="Request password reset"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p class="note mt3">
|
<div class="note mt3 mw-none mw5-ns">
|
||||||
Note: To avoid your being spammed with password reset links, we limit the number of requests per account every 24 hours.
|
Note: To avoid spamming you with password reset links, we limit the number of requests per account every 24 hours.
|
||||||
</p>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
{{ template "base.html" . }}
|
{{ template "base.html" . }}
|
||||||
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<div class="content-block">
|
<div class="mw6 ph3 ph0-ns pb3">
|
||||||
<h4>A password reset link was sent. It'll expire in 24 hours.</h4>
|
<h1>A password reset link was sent.</h1>
|
||||||
<p>If for some reason the email shouldn't arrive in a timely fashion, and it also doesn't show up in your spam trap, please <a href="{{ .ContactUsUrl }}">contact the staff</a>.</p>
|
<p>
|
||||||
<p><small>* Security best practices prevent us from disclosing whether or not the username and email combination was actually valid. An email may or may not have been sent depending on whether it was or wasn't.</small></p>
|
It will expire in 24 hours.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
If for some reason the email doesn't arrive in a timely fashion, and it also doesn't show up in your spam, please <a href="{{ .ContactUsUrl }}">contact the staff</a>.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
@ -1,58 +1,58 @@
|
||||||
{{ template "base.html" . }}
|
{{ template "base.html" . }}
|
||||||
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<div class="content-block">
|
<div class="flex ph3 ph0-ns justify-center">
|
||||||
<form id="register_form" method="post">
|
<div class="w-100 w-auto-ns pv3">
|
||||||
|
<h1 class="tc">Sign up</h1>
|
||||||
|
<form method="POST" class="flex flex-column">
|
||||||
{{/* NOTE(asaf): No CSRF on register. We don't have a user session yet and I don't think we would gain anything from a pre-login session here */}}
|
{{/* NOTE(asaf): No CSRF on register. We don't have a user session yet and I don't think we would gain anything from a pre-login session here */}}
|
||||||
|
|
||||||
<p class="mb2">
|
<input type="hidden" name="destination" value="{{ .DestinationURL }}" />
|
||||||
|
|
||||||
|
<div>
|
||||||
<label class="db b" for="username">Username</label>
|
<label class="db b" for="username">Username</label>
|
||||||
<input class="db w5" name="username" minlength="3" maxlength="30" type="text" required pattern="^[0-9a-zA-Z][\w-]{2,29}$" />
|
<input class="db w-100 w5-ns"
|
||||||
<span class="note db">Required. You may use up to 30 characters. Must start with a letter or number. Dashes and underscores are allowed.</span>
|
name="username"
|
||||||
</p>
|
type="text"
|
||||||
|
minlength="3" maxlength="30"
|
||||||
|
pattern="^[0-9a-zA-Z][\w-]{2,29}$"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p class="mb2">
|
<div class="mt2">
|
||||||
<label class="db b" for="displayname">Display name</label>
|
<label class="db" for="displayname"><b>Display name</b> <span class="c--dim i">(optional)</span></label>
|
||||||
<input class="db w5" name="displayname" type="text" />
|
<input class="db w-100 w5-ns"
|
||||||
<span class="note db">Optional.</span>
|
name="displayname"
|
||||||
</p>
|
type="text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p class="mb2">
|
<div class="mt2">
|
||||||
<label class="db b" for="email">Email</label>
|
<label class="db b" for="email">Email</label>
|
||||||
<input class="db w5" name="email" type="email" required />
|
<input class="db w-100 w5-ns"
|
||||||
</p>
|
name="email"
|
||||||
|
type="email"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p class="mb2">
|
<div class="mt2">
|
||||||
<label class="db b" for="password">Password</label>
|
<label class="db b" for="password">Password</label>
|
||||||
<input class="db w5" name="password" minlength="8" type="password" required />
|
<input class="db w-100 w5-ns"
|
||||||
</p>
|
name="password"
|
||||||
|
type="password"
|
||||||
<p class="mb2">
|
minlength="8"
|
||||||
<label class="db b" for="password2">Password confirmation</label>
|
required
|
||||||
<input class="db w5" name="password2" minlength="8" type="password" required />
|
/>
|
||||||
<span class="note db">Enter the same password as before, for verification.</span>
|
</div>
|
||||||
</p>
|
|
||||||
|
|
||||||
{{/* TODO(asaf): Consider adding some bot-mitigation thing here */}}
|
{{/* TODO(asaf): Consider adding some bot-mitigation thing here */}}
|
||||||
|
|
||||||
<p>
|
<div class="mt3">
|
||||||
<input type="submit" value="Register" />
|
<input class="w-100" type="submit" value="Register" />
|
||||||
</p>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
<script>
|
</div>
|
||||||
let form = document.querySelector("#register_form")
|
|
||||||
let password1 = form.querySelector("input[name=password]")
|
|
||||||
let password2 = form.querySelector("input[name=password2]")
|
|
||||||
password1.addEventListener("input", validatePasswordConfirmation);
|
|
||||||
password2.addEventListener("input", validatePasswordConfirmation);
|
|
||||||
|
|
||||||
function validatePasswordConfirmation(ev) {
|
|
||||||
if (password1.value != password2.value) {
|
|
||||||
password2.setCustomValidity("Password doesn't match");
|
|
||||||
} else {
|
|
||||||
password2.setCustomValidity("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
{{ template "base.html" . }}
|
{{ template "base.html" . }}
|
||||||
|
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<div class="content-block">
|
<div class="mw7 ph3 ph0-ns">
|
||||||
<h1>Hi! You're almost done signing up.</h1>
|
<h1>Verify your email</h1>
|
||||||
<p class="mb3 b">We've sent you an email with a confirmation link. Please follow it to complete the registration process.</p>
|
<p class="mb3 b">
|
||||||
<p>If for some reason the email doesn't arrive in a timely fashion and you also can't find it in your spam trap,<br />
|
We've sent you an email with a confirmation link. Please follow it to complete the registration process.
|
||||||
you should feel free to <a href="{{ .ContactUsUrl }}">contact the staff</a> and ask us to activate you manually.<br />
|
</p>
|
||||||
You'll want to tell us the username you chose and preferably email us from the same address you used to sign up.</p>
|
<p>
|
||||||
|
If for some reason the email doesn't arrive in a timely fashion and you also can't find it in your spam trap, you should feel free to <a href="{{ .ContactUsUrl }}">contact the staff</a> and ask us to activate you manually.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Please tell us your username, and ideally email us from the same address you used to sign up.
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
@ -1,10 +1,22 @@
|
||||||
<p>Hello, {{ .Name }}</p>
|
<p>
|
||||||
<p>Someone has requested a password reset for your account.<br />
|
Hello {{ .Name }},
|
||||||
We hope it was you. If you didn't request it, we apologise. Kindly ignore this message.</p>
|
</p>
|
||||||
<p>To finish the password reset, visit: <a href="{{ .DoPasswordResetUrl }}"><b>{{ .DoPasswordResetUrl }}</b></a><br />
|
<p>
|
||||||
This link will be valid for 24 hours (until {{ absolutedate .Expiration }} UTC)</p>
|
Someone has requested a password reset for your Handmade Network account. To finish the password reset, visit the following link:
|
||||||
<p>Thanks,<br />
|
</p>
|
||||||
The Handmade Network staff.</p>
|
<p>
|
||||||
|
<a href="{{ .DoPasswordResetUrl }}">{{ .DoPasswordResetUrl }}</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This link will be valid for 24 hours (until {{ absolutedate .Expiration }} UTC).
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
If you did not request this password reset, we apologize - please ignore this message.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Thanks,<br />
|
||||||
|
The Handmade Network staff.
|
||||||
|
</p>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
<p style="font-size:small; -webkit-text-size-adjust:none; color: #666">
|
<p style="font-size:small; -webkit-text-size-adjust:none; color: #666">
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
<p>Hello, {{ .Name }}</p>
|
<p>
|
||||||
<p>Welcome to <a href="{{ .HomepageUrl }}"><b>Handmade Network</b></a>.<br />
|
Hello {{ .Name }} - welcome to <a href="{{ .HomepageUrl }}"><b>Handmade Network</b></a>!
|
||||||
To complete the registration process, please use the following link: <a href="{{ .CompleteRegistrationUrl }}"><b>{{ .CompleteRegistrationUrl }}</b></a>.</p>
|
</p>
|
||||||
|
<p>
|
||||||
|
To complete the registration process, please use the following link:
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a href="{{ .CompleteRegistrationUrl }}">{{ .CompleteRegistrationUrl }}</a>.
|
||||||
|
</p>
|
||||||
<p>Thanks,<br />
|
<p>Thanks,<br />
|
||||||
The Handmade Network staff.</p>
|
The Handmade Network staff.</p>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
<p style="font-size:small; -webkit-text-size-adjust:none; color: #666">
|
<p style="font-size:small; -webkit-text-size-adjust:none; color: #666">
|
||||||
You are receiving this email because someone registered with your email address at <a href="{{ .HomepageUrl }}">Handmade.Network</a>.<br />
|
You are receiving this email because someone registered with your email address at <a href="{{ .HomepageUrl }}">handmade.network</a>. If that wasn't you, kindly ignore this email. If you do not complete the registration, your information will be deleted from our servers after 7 days.
|
||||||
If that wasn't you, kindly ignore this email. If you do not complete the registration, your information will be deleted from our servers after 7 days.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
|
@ -5,8 +5,9 @@
|
||||||
{{ $themeDimmer := eq .Theme "dark" | ternary (lightness 0.3 $c) (lightness 0.8 $c) | color2css }}
|
{{ $themeDimmer := eq .Theme "dark" | ternary (lightness 0.3 $c) (lightness 0.8 $c) | color2css }}
|
||||||
{{ $themeDimmest := eq .Theme "dark" | ternary (lightness 0.2 $c) (lightness 0.85 $c) | color2css }}
|
{{ $themeDimmest := eq .Theme "dark" | ternary (lightness 0.2 $c) (lightness 0.85 $c) | color2css }}
|
||||||
|
|
||||||
{{/* Theme "dark" colors are always darker in value, regardless of light or dark theme. */}}
|
{{/* Theme "dark" and "light" colors are always darker or lighter in value, regardless of theme. */}}
|
||||||
{{ $themeDark := eq .Theme "dark" | ternary (lightness 0.30 $c) (lightness 0.35 $c) | color2css }}
|
{{ $themeDark := eq .Theme "dark" | ternary (lightness 0.30 $c) (lightness 0.35 $c) | color2css }}
|
||||||
|
{{ $themeLight := eq .Theme "dark" | ternary (lightness 0.55 $c) (lightness 0.55 $c) | color2css }}
|
||||||
|
|
||||||
{{ $linkColor := eq .Theme "dark" | ternary (lightness 0.55 $c) (lightness 0.35 $c) | color2css }}
|
{{ $linkColor := eq .Theme "dark" | ternary (lightness 0.55 $c) (lightness 0.35 $c) | color2css }}
|
||||||
{{ $linkHoverColor := eq .Theme "dark" | ternary (lightness 0.65 $c) (lightness 0.45 $c) | color2css }}
|
{{ $linkHoverColor := eq .Theme "dark" | ternary (lightness 0.65 $c) (lightness 0.45 $c) | color2css }}
|
||||||
|
@ -18,6 +19,7 @@
|
||||||
--theme-color-dimmest: {{ $themeDimmest }};
|
--theme-color-dimmest: {{ $themeDimmest }};
|
||||||
|
|
||||||
--theme-color-dark: {{ $themeDark }};
|
--theme-color-dark: {{ $themeDark }};
|
||||||
|
--theme-color-light: {{ $themeLight }};
|
||||||
|
|
||||||
--link-color: {{ $linkColor }};
|
--link-color: {{ $linkColor }};
|
||||||
--link-hover-color: {{ $linkHoverColor }};
|
--link-hover-color: {{ $linkHoverColor }};
|
||||||
|
@ -89,6 +91,11 @@ a:hover, button:hover, .button:hover, input[type=button]:hover {
|
||||||
background-color: var(--theme-color-dark);
|
background-color: var(--theme-color-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bg-theme-light {
|
||||||
|
background-color: {{ $themeLight }};
|
||||||
|
background-color: var(--theme-color-light);
|
||||||
|
}
|
||||||
|
|
||||||
.b--theme {
|
.b--theme {
|
||||||
border-color: {{ $c | color2css }};
|
border-color: {{ $c | color2css }};
|
||||||
border-color: var(--theme-color);
|
border-color: var(--theme-color);
|
||||||
|
@ -114,6 +121,11 @@ a:hover, button:hover, .button:hover, input[type=button]:hover {
|
||||||
border-color: var(--theme-color-dark);
|
border-color: var(--theme-color-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.b--theme-light {
|
||||||
|
border-color: {{ $themeLight }};
|
||||||
|
border-color: var(--theme-color-light);
|
||||||
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--background-even-background: {{ .PostBgColor }};
|
--background-even-background: {{ .PostBgColor }};
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,19 +90,13 @@
|
||||||
<div class="edit-form-row">
|
<div class="edit-form-row">
|
||||||
<div class="pt-input-ns">New password:</div>
|
<div class="pt-input-ns">New password:</div>
|
||||||
<div>
|
<div>
|
||||||
<input id="id_new_password1" name="new_password1" type="password" />
|
<input name="new_password" type="password" />
|
||||||
<div class="c--dim f7 mw6">
|
<div class="c--dim f7 mw6">
|
||||||
Your password must be 8 or more characters, and must differ from your username and current password.
|
Your password must be 8 or more characters, and must differ from your username and current password.
|
||||||
Other than that, <a href="http://krebsonsecurity.com/password-dos-and-donts/" class="external" target="_blank">please follow best practices</a>.
|
Other than that, <a href="http://krebsonsecurity.com/password-dos-and-donts/" class="external" target="_blank">please follow best practices</a>.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="edit-form-row">
|
|
||||||
<div class="pt-input-ns">New password confirmation:</div>
|
|
||||||
<div>
|
|
||||||
<input id="id_new_password2" name="new_password2" type="password" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="edit-form-row">
|
<div class="edit-form-row">
|
||||||
<div></div>
|
<div></div>
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.handmade.network/hmn/hmn/src/auth"
|
"git.handmade.network/hmn/hmn/src/auth"
|
||||||
|
"git.handmade.network/hmn/hmn/src/config"
|
||||||
"git.handmade.network/hmn/hmn/src/db"
|
"git.handmade.network/hmn/hmn/src/db"
|
||||||
"git.handmade.network/hmn/hmn/src/email"
|
"git.handmade.network/hmn/hmn/src/email"
|
||||||
"git.handmade.network/hmn/hmn/src/hmnurl"
|
"git.handmade.network/hmn/hmn/src/hmnurl"
|
||||||
|
@ -23,6 +24,7 @@ var UsernameRegex = regexp.MustCompile(`^[0-9a-zA-Z][\w-]{2,29}$`)
|
||||||
type LoginPageData struct {
|
type LoginPageData struct {
|
||||||
templates.BaseData
|
templates.BaseData
|
||||||
RedirectUrl string
|
RedirectUrl string
|
||||||
|
RegisterUrl string
|
||||||
ForgotPasswordUrl string
|
ForgotPasswordUrl string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,8 +35,9 @@ func LoginPage(c *RequestContext) ResponseData {
|
||||||
|
|
||||||
var res ResponseData
|
var res ResponseData
|
||||||
res.MustWriteTemplate("auth_login.html", LoginPageData{
|
res.MustWriteTemplate("auth_login.html", LoginPageData{
|
||||||
BaseData: getBaseDataAutocrumb(c, "Log in"),
|
BaseData: getBaseData(c, "Log in", nil),
|
||||||
RedirectUrl: c.Req.URL.Query().Get("redirect"),
|
RedirectUrl: c.Req.URL.Query().Get("redirect"),
|
||||||
|
RegisterUrl: hmnurl.BuildRegister(),
|
||||||
ForgotPasswordUrl: hmnurl.BuildRequestPasswordReset(),
|
ForgotPasswordUrl: hmnurl.BuildRequestPasswordReset(),
|
||||||
}, c.Perf)
|
}, c.Perf)
|
||||||
return res
|
return res
|
||||||
|
@ -47,12 +50,14 @@ func Login(c *RequestContext) ResponseData {
|
||||||
}
|
}
|
||||||
|
|
||||||
redirect := form.Get("redirect")
|
redirect := form.Get("redirect")
|
||||||
if redirect == "" {
|
|
||||||
redirect = "/"
|
destination := hmnurl.BuildHomepage()
|
||||||
|
if redirect != "" && urlIsLocal(redirect) {
|
||||||
|
destination = redirect
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.CurrentUser != nil {
|
if c.CurrentUser != nil {
|
||||||
res := c.Redirect(redirect, http.StatusSeeOther)
|
res := c.Redirect(destination, http.StatusSeeOther)
|
||||||
res.AddFutureNotice("warn", fmt.Sprintf("You are already logged in as %s.", c.CurrentUser.Username))
|
res.AddFutureNotice("warn", fmt.Sprintf("You are already logged in as %s.", c.CurrentUser.Username))
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
@ -65,7 +70,7 @@ func Login(c *RequestContext) ResponseData {
|
||||||
|
|
||||||
showLoginWithFailure := func(c *RequestContext, redirect string) ResponseData {
|
showLoginWithFailure := func(c *RequestContext, redirect string) ResponseData {
|
||||||
var res ResponseData
|
var res ResponseData
|
||||||
baseData := getBaseDataAutocrumb(c, "Log in")
|
baseData := getBaseData(c, "Log in", nil)
|
||||||
baseData.AddImmediateNotice("failure", "Incorrect username or password")
|
baseData.AddImmediateNotice("failure", "Incorrect username or password")
|
||||||
res.MustWriteTemplate("auth_login.html", LoginPageData{
|
res.MustWriteTemplate("auth_login.html", LoginPageData{
|
||||||
BaseData: baseData,
|
BaseData: baseData,
|
||||||
|
@ -105,7 +110,7 @@ func Login(c *RequestContext) ResponseData {
|
||||||
return c.RejectRequest("You must validate your email address before logging in. You should've received an email shortly after registration. If you did not receive the email, please contact the staff.")
|
return c.RejectRequest("You must validate your email address before logging in. You should've received an email shortly after registration. If you did not receive the email, please contact the staff.")
|
||||||
}
|
}
|
||||||
|
|
||||||
res := c.Redirect(redirect, http.StatusSeeOther)
|
res := c.Redirect(destination, http.StatusSeeOther)
|
||||||
err = loginUser(c, user, &res)
|
err = loginUser(c, user, &res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.ErrorResponse(http.StatusInternalServerError, err)
|
return c.ErrorResponse(http.StatusInternalServerError, err)
|
||||||
|
@ -114,12 +119,14 @@ func Login(c *RequestContext) ResponseData {
|
||||||
}
|
}
|
||||||
|
|
||||||
func Logout(c *RequestContext) ResponseData {
|
func Logout(c *RequestContext) ResponseData {
|
||||||
redir := c.Req.URL.Query().Get("redirect")
|
redirect := c.Req.URL.Query().Get("redirect")
|
||||||
if redir == "" {
|
|
||||||
redir = "/"
|
destination := hmnurl.BuildHomepage()
|
||||||
|
if redirect != "" && urlIsLocal(redirect) {
|
||||||
|
destination = redirect
|
||||||
}
|
}
|
||||||
|
|
||||||
res := c.Redirect(redir, http.StatusSeeOther)
|
res := c.Redirect(destination, http.StatusSeeOther)
|
||||||
logoutUser(c, &res)
|
logoutUser(c, &res)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
@ -128,9 +135,21 @@ func RegisterNewUser(c *RequestContext) ResponseData {
|
||||||
if c.CurrentUser != nil {
|
if c.CurrentUser != nil {
|
||||||
c.Redirect(hmnurl.BuildUserSettings(c.CurrentUser.Username), http.StatusSeeOther)
|
c.Redirect(hmnurl.BuildUserSettings(c.CurrentUser.Username), http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(asaf): Do something to prevent bot registration
|
// TODO(asaf): Do something to prevent bot registration
|
||||||
|
|
||||||
|
type RegisterPageData struct {
|
||||||
|
templates.BaseData
|
||||||
|
DestinationURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl := RegisterPageData{
|
||||||
|
BaseData: getBaseData(c, "Register", nil),
|
||||||
|
DestinationURL: c.Req.URL.Query().Get("destination"),
|
||||||
|
}
|
||||||
|
|
||||||
var res ResponseData
|
var res ResponseData
|
||||||
res.MustWriteTemplate("auth_register.html", getBaseDataAutocrumb(c, "Register"), c.Perf)
|
res.MustWriteTemplate("auth_register.html", tmpl, c.Perf)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,7 +163,7 @@ func RegisterNewUserSubmit(c *RequestContext) ResponseData {
|
||||||
displayName := strings.TrimSpace(c.Req.Form.Get("displayname"))
|
displayName := strings.TrimSpace(c.Req.Form.Get("displayname"))
|
||||||
emailAddress := strings.TrimSpace(c.Req.Form.Get("email"))
|
emailAddress := strings.TrimSpace(c.Req.Form.Get("email"))
|
||||||
password := c.Req.Form.Get("password")
|
password := c.Req.Form.Get("password")
|
||||||
password2 := c.Req.Form.Get("password2")
|
destination := strings.TrimSpace(c.Req.Form.Get("destination"))
|
||||||
if !UsernameRegex.Match([]byte(username)) {
|
if !UsernameRegex.Match([]byte(username)) {
|
||||||
return c.RejectRequest("Invalid username")
|
return c.RejectRequest("Invalid username")
|
||||||
}
|
}
|
||||||
|
@ -154,9 +173,6 @@ func RegisterNewUserSubmit(c *RequestContext) ResponseData {
|
||||||
if len(password) < 8 {
|
if len(password) < 8 {
|
||||||
return c.RejectRequest("Password too short")
|
return c.RejectRequest("Password too short")
|
||||||
}
|
}
|
||||||
if password != password2 {
|
|
||||||
return c.RejectRequest("Password confirmation doesn't match password")
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Perf.StartBlock("SQL", "Check blacklist")
|
c.Perf.StartBlock("SQL", "Check blacklist")
|
||||||
// TODO(asaf): Check email against blacklist
|
// TODO(asaf): Check email against blacklist
|
||||||
|
@ -257,7 +273,14 @@ func RegisterNewUserSubmit(c *RequestContext) ResponseData {
|
||||||
if mailName == "" {
|
if mailName == "" {
|
||||||
mailName = username
|
mailName = username
|
||||||
}
|
}
|
||||||
err = email.SendRegistrationEmail(emailAddress, mailName, username, ott, c.Perf)
|
err = email.SendRegistrationEmail(
|
||||||
|
emailAddress,
|
||||||
|
mailName,
|
||||||
|
username,
|
||||||
|
ott,
|
||||||
|
destination,
|
||||||
|
c.Perf,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to send registration email"))
|
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to send registration email"))
|
||||||
}
|
}
|
||||||
|
@ -283,7 +306,7 @@ func RegisterNewUserSuccess(c *RequestContext) ResponseData {
|
||||||
|
|
||||||
var res ResponseData
|
var res ResponseData
|
||||||
res.MustWriteTemplate("auth_register_success.html", RegisterNewUserSuccessData{
|
res.MustWriteTemplate("auth_register_success.html", RegisterNewUserSuccessData{
|
||||||
BaseData: getBaseDataAutocrumb(c, "Register"),
|
BaseData: getBaseData(c, "Register", nil),
|
||||||
ContactUsUrl: hmnurl.BuildContactPage(),
|
ContactUsUrl: hmnurl.BuildContactPage(),
|
||||||
}, c.Perf)
|
}, c.Perf)
|
||||||
return res
|
return res
|
||||||
|
@ -293,6 +316,7 @@ type EmailValidationData struct {
|
||||||
templates.BaseData
|
templates.BaseData
|
||||||
Token string
|
Token string
|
||||||
Username string
|
Username string
|
||||||
|
DestinationURL string
|
||||||
}
|
}
|
||||||
|
|
||||||
func EmailConfirmation(c *RequestContext) ResponseData {
|
func EmailConfirmation(c *RequestContext) ResponseData {
|
||||||
|
@ -329,9 +353,10 @@ func EmailConfirmation(c *RequestContext) ResponseData {
|
||||||
|
|
||||||
var res ResponseData
|
var res ResponseData
|
||||||
res.MustWriteTemplate("auth_email_validation.html", EmailValidationData{
|
res.MustWriteTemplate("auth_email_validation.html", EmailValidationData{
|
||||||
BaseData: getBaseDataAutocrumb(c, "Register"),
|
BaseData: getBaseData(c, "Register", nil),
|
||||||
Token: token,
|
Token: token,
|
||||||
Username: username,
|
Username: username,
|
||||||
|
DestinationURL: c.Req.URL.Query().Get("destination"),
|
||||||
}, c.Perf)
|
}, c.Perf)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
@ -342,6 +367,7 @@ func EmailConfirmationSubmit(c *RequestContext) ResponseData {
|
||||||
token := c.Req.Form.Get("token")
|
token := c.Req.Form.Get("token")
|
||||||
username := c.Req.Form.Get("username")
|
username := c.Req.Form.Get("username")
|
||||||
password := c.Req.Form.Get("password")
|
password := c.Req.Form.Get("password")
|
||||||
|
destination := c.Req.Form.Get("destination")
|
||||||
|
|
||||||
validationResult := validateUsernameAndToken(c, username, token, models.TokenTypeRegistration)
|
validationResult := validateUsernameAndToken(c, username, token, models.TokenTypeRegistration)
|
||||||
if !validationResult.Match {
|
if !validationResult.Match {
|
||||||
|
@ -354,7 +380,7 @@ func EmailConfirmationSubmit(c *RequestContext) ResponseData {
|
||||||
return c.ErrorResponse(http.StatusInternalServerError, err)
|
return c.ErrorResponse(http.StatusInternalServerError, err)
|
||||||
} else if !success {
|
} else if !success {
|
||||||
var res ResponseData
|
var res ResponseData
|
||||||
baseData := getBaseDataAutocrumb(c, "Register")
|
baseData := getBaseData(c, "Register", nil)
|
||||||
// NOTE(asaf): We can report that the password is incorrect, because an attacker wouldn't have a valid token to begin with.
|
// NOTE(asaf): We can report that the password is incorrect, because an attacker wouldn't have a valid token to begin with.
|
||||||
baseData.AddImmediateNotice("failure", "Incorrect password. Please try again.")
|
baseData.AddImmediateNotice("failure", "Incorrect password. Please try again.")
|
||||||
res.MustWriteTemplate("auth_email_validation.html", EmailValidationData{
|
res.MustWriteTemplate("auth_email_validation.html", EmailValidationData{
|
||||||
|
@ -401,7 +427,12 @@ func EmailConfirmationSubmit(c *RequestContext) ResponseData {
|
||||||
}
|
}
|
||||||
c.Perf.EndBlock()
|
c.Perf.EndBlock()
|
||||||
|
|
||||||
res := c.Redirect(hmnurl.BuildHomepage(), http.StatusSeeOther)
|
redirect := hmnurl.BuildHomepage()
|
||||||
|
if destination != "" && urlIsLocal(destination) {
|
||||||
|
redirect = destination
|
||||||
|
}
|
||||||
|
|
||||||
|
res := c.Redirect(redirect, http.StatusSeeOther)
|
||||||
res.AddFutureNotice("success", "You've completed your registration successfully!")
|
res.AddFutureNotice("success", "You've completed your registration successfully!")
|
||||||
err = loginUser(c, validationResult.User, &res)
|
err = loginUser(c, validationResult.User, &res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -432,7 +463,7 @@ func RequestPasswordReset(c *RequestContext) ResponseData {
|
||||||
return c.Redirect(hmnurl.BuildHomepage(), http.StatusSeeOther)
|
return c.Redirect(hmnurl.BuildHomepage(), http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
var res ResponseData
|
var res ResponseData
|
||||||
res.MustWriteTemplate("auth_password_reset.html", getBaseDataAutocrumb(c, "Password Reset"), c.Perf)
|
res.MustWriteTemplate("auth_password_reset.html", getBaseData(c, "Password Reset", nil), c.Perf)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -550,7 +581,7 @@ func PasswordResetSent(c *RequestContext) ResponseData {
|
||||||
}
|
}
|
||||||
var res ResponseData
|
var res ResponseData
|
||||||
res.MustWriteTemplate("auth_password_reset_sent.html", PasswordResetSentData{
|
res.MustWriteTemplate("auth_password_reset_sent.html", PasswordResetSentData{
|
||||||
BaseData: getBaseDataAutocrumb(c, "Password Reset"),
|
BaseData: getBaseData(c, "Password Reset", nil),
|
||||||
ContactUsUrl: hmnurl.BuildContactPage(),
|
ContactUsUrl: hmnurl.BuildContactPage(),
|
||||||
}, c.Perf)
|
}, c.Perf)
|
||||||
return res
|
return res
|
||||||
|
@ -584,7 +615,7 @@ func DoPasswordReset(c *RequestContext) ResponseData {
|
||||||
}
|
}
|
||||||
|
|
||||||
res.MustWriteTemplate("auth_do_password_reset.html", DoPasswordResetData{
|
res.MustWriteTemplate("auth_do_password_reset.html", DoPasswordResetData{
|
||||||
BaseData: getBaseDataAutocrumb(c, "Password Reset"),
|
BaseData: getBaseData(c, "Password Reset", nil),
|
||||||
Username: username,
|
Username: username,
|
||||||
Token: token,
|
Token: token,
|
||||||
}, c.Perf)
|
}, c.Perf)
|
||||||
|
@ -597,7 +628,6 @@ func DoPasswordResetSubmit(c *RequestContext) ResponseData {
|
||||||
token := c.Req.Form.Get("token")
|
token := c.Req.Form.Get("token")
|
||||||
username := c.Req.Form.Get("username")
|
username := c.Req.Form.Get("username")
|
||||||
password := c.Req.Form.Get("password")
|
password := c.Req.Form.Get("password")
|
||||||
password2 := c.Req.Form.Get("password2")
|
|
||||||
|
|
||||||
validationResult := validateUsernameAndToken(c, username, token, models.TokenTypePasswordReset)
|
validationResult := validateUsernameAndToken(c, username, token, models.TokenTypePasswordReset)
|
||||||
if !validationResult.Match {
|
if !validationResult.Match {
|
||||||
|
@ -611,9 +641,6 @@ func DoPasswordResetSubmit(c *RequestContext) ResponseData {
|
||||||
if len(password) < 8 {
|
if len(password) < 8 {
|
||||||
return c.RejectRequest("Password too short")
|
return c.RejectRequest("Password too short")
|
||||||
}
|
}
|
||||||
if password != password2 {
|
|
||||||
return c.RejectRequest("Password confirmation doesn't match password")
|
|
||||||
}
|
|
||||||
|
|
||||||
hashed := auth.HashPassword(password)
|
hashed := auth.HashPassword(password)
|
||||||
|
|
||||||
|
@ -806,3 +833,7 @@ func validateUsernameAndToken(c *RequestContext, username string, token string,
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func urlIsLocal(url string) bool {
|
||||||
|
return strings.HasPrefix(url, config.Config.BaseUrl)
|
||||||
|
}
|
||||||
|
|
|
@ -405,10 +405,9 @@ func UserSettingsSave(c *RequestContext) ResponseData {
|
||||||
|
|
||||||
// Update password
|
// Update password
|
||||||
oldPassword := form.Get("old_password")
|
oldPassword := form.Get("old_password")
|
||||||
newPassword := form.Get("new_password1")
|
newPassword := form.Get("new_password")
|
||||||
newPasswordConfirmation := form.Get("new_password2")
|
|
||||||
if oldPassword != "" && newPassword != "" {
|
if oldPassword != "" && newPassword != "" {
|
||||||
errorRes := updatePassword(c, tx, oldPassword, newPassword, newPasswordConfirmation)
|
errorRes := updatePassword(c, tx, oldPassword, newPassword)
|
||||||
if errorRes != nil {
|
if errorRes != nil {
|
||||||
return *errorRes
|
return *errorRes
|
||||||
}
|
}
|
||||||
|
@ -526,12 +525,7 @@ func UserProfileAdminNuke(c *RequestContext) ResponseData {
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func updatePassword(c *RequestContext, tx pgx.Tx, old, new, confirm string) *ResponseData {
|
func updatePassword(c *RequestContext, tx pgx.Tx, old, new string) *ResponseData {
|
||||||
if new != confirm {
|
|
||||||
res := c.RejectRequest("Your password and password confirmation did not match.")
|
|
||||||
return &res
|
|
||||||
}
|
|
||||||
|
|
||||||
oldHashedPassword, err := auth.ParsePasswordString(c.CurrentUser.Password)
|
oldHashedPassword, err := auth.ParsePasswordString(c.CurrentUser.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Logger.Warn().Err(err).Msg("failed to parse user's password string")
|
c.Logger.Warn().Err(err).Msg("failed to parse user's password string")
|
||||||
|
|
Loading…
Reference in New Issue