Update auth flows

This commit is contained in:
Ben Visness 2022-08-13 14:15:00 -05:00
parent 4f1989f663
commit a0cc2f5c66
25 changed files with 1295 additions and 1140 deletions

File diff suppressed because it is too large Load Diff

View File

@ -216,6 +216,8 @@ pre, code, .codeblock {
--theme-color-dim: #444;
--theme-color-dimmer: #383838;
--theme-color-dimmest: #333;
--theme-color-dark: #666;
--theme-color-light: #666;
--link-color: #aaa;
--link-border-color: #aaa;
--hr-color: #aaa;

View File

@ -234,6 +234,8 @@ pre, code, .codeblock {
--theme-color-dim: #aaa;
--theme-color-dimmer: #bbb;
--theme-color-dimmest: #ccc;
--theme-color-dark: #666;
--theme-color-light: #666;
--link-color: #666;
--link-border-color: #666;
--hr-color: #444;

View File

@ -286,7 +286,7 @@ func init() {
var err error
switch emailType {
case "registration":
err = email.SendRegistrationEmail(toAddress, toName, "test_user", "test_token", p)
err = email.SendRegistrationEmail(toAddress, toName, "test_user", "test_token", "", p)
case "passwordreset":
err = email.SendPasswordReset(toAddress, toName, "test_user", "test_token", time.Now().Add(time.Hour*24), p)
default:

View File

@ -26,14 +26,21 @@ type RegistrationEmailData struct {
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", "Rendering template")
contents, err := renderTemplate("email_registration.html", RegistrationEmailData{
Name: toName,
HomepageUrl: hmnurl.BuildHomepage(),
CompleteRegistrationUrl: hmnurl.BuildEmailConfirmation(username, completionToken),
CompleteRegistrationUrl: hmnurl.BuildEmailConfirmation(username, completionToken, destination),
})
if err != nil {
return err

View File

@ -28,7 +28,7 @@ var WRJ2021 = Jam{
var WRJ2022 = Jam{
Name: "Wheel Reinvention Jam 2022",
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"))),
}

View File

@ -119,9 +119,13 @@ func BuildRegistrationSuccess() string {
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()
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$")

View File

@ -163,8 +163,8 @@ input, select, textarea {
&:hover {
@include usevar('color', 'form-button-color-active');
@include usevar('background-color', 'theme-color-dim');
@include usevar('border-color', 'theme-color-dim');
@include usevar('background-color', 'theme-color-light');
@include usevar('border-color', 'theme-color-light');
}
&.lite {

View File

@ -43,6 +43,8 @@ $width-3: 4rem !default;
$width-4: 8rem !default;
$width-5: 16rem !default;
$width-6: 32rem !default;
$width-7: 48rem !default;
$width-8: 64rem !default;
$max-width-1: 1rem !default;
$max-width-2: 2rem !default;
$max-width-3: 4rem !default;

View File

@ -55,6 +55,8 @@
.w4 { width: $width-4; }
.w5 { width: $width-5; }
.w6 { width: $width-6; }
.w7 { width: $width-7; }
.w8 { width: $width-8; }
.w-10 { width: 10%; }
.w-20 { width: 20%; }
@ -82,6 +84,8 @@
.w4-ns { width: $width-4; }
.w5-ns { width: $width-5; }
.w6-ns { width: $width-6; }
.w7-ns { width: $width-7; }
.w8-ns { width: $width-8; }
.w-10-ns { width: 10%; }
.w-20-ns { width: 20%; }
.w-25-ns { width: 25%; }
@ -108,6 +112,8 @@
.w4-m { width: $width-4; }
.w5-m { width: $width-5; }
.w6-m { width: $width-6; }
.w7-m { width: $width-7; }
.w8-m { width: $width-8; }
.w-10-m { width: 10%; }
.w-20-m { width: 20%; }
.w-25-m { width: 25%; }
@ -134,6 +140,8 @@
.w4-l { width: $width-4; }
.w5-l { width: $width-5; }
.w6-l { width: $width-6; }
.w7-l { width: $width-7; }
.w8-l { width: $width-8; }
.w-10-l { width: 10%; }
.w-20-l { width: 20%; }
.w-25-l { width: 25%; }

View File

@ -9,6 +9,9 @@ $vars: (
theme-color-dimmer: #383838,
theme-color-dimmest: #333,
theme-color-dark: #666,
theme-color-light: #666,
link-color: #aaa,
link-border-color: #aaa,

View File

@ -9,6 +9,9 @@ $vars: (
theme-color-dimmer: #bbb,
theme-color-dimmest: #ccc,
theme-color-dark: #666,
theme-color-light: #666,
link-color: #666,
link-border-color: #666,

View File

@ -1,41 +1,38 @@
{{ template "base.html" . }}
{{ define "content" }}
<div class="content-block">
<form id="password_reset_form" method="post">
<div class="flex ph3 ph0-ns justify-center">
<div class="w-100 w-auto-ns pv3">
<h1>Hi, {{ .Username }}!</h1>
<form method="post" class="flex flex-column">
<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 />
<input
style="position:absolute; visibility:hidden;"
type="text"
name="username"
value="{{ .Username }}"
readonly
/>
<h1>Hi, {{ .Username }}!</h1>
<p class="mb3 b">Please enter a new password</p>
<p class="mb2">
<label class="db b" for="password">New Password</label>
<input class="db" type="password" name="password" minlength="8" required />
</p>
<div>Please enter a new password.</div>
<p class="mb2">
<label class="db b" for="password2">New password confirmation</label>
<input class="db" type="password" name="password2" minlength="8" required />
<span class="note db">Enter the same password as before, for verification.</span>
</p>
<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" type="submit" value="Reset your password" />
<input class="db mt3 w-100"
type="submit"
value="Reset your password"
/>
</form>
</div>
<script>
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>
</div>
{{ end }}

View File

@ -1,19 +1,39 @@
{{ template "base.html" . }}
{{ define "content" }}
<div class="content-block">
<form method="POST">
<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 />
<div class="mw7 ph3 ph0-ns pb3">
<form method="POST">
<input type="hidden" name="token" value="{{ .Token }}" />
<input type="hidden" name="destination" value="{{ .DestinationURL }}" />
<h1>Hi, {{ .Username }}!</h1>
<p class="mb3 b">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>
<input class="db" type="password" name="password" minlength="8" required />
{{/*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;"
name="username"
type="text"
value="{{ .Username }}"
readonly
/>
<input class="db mt3" type="submit" value="Complete registration" />
</form>
</div>
<h1>Hi, {{ .Username }}!</h1>
<p class="mb3 b">
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>
<input class="db w-100 w-auto-ns"
name="password"
type="password"
minlength="8"
required
/>
<input class="db mt3 w-100 w-auto-ns"
type="submit"
value="Complete registration"
/>
</form>
</div>
{{ end }}

View File

@ -1,25 +1,43 @@
{{ template "base.html" . }}
{{ define "content" }}
<div class="content-block">
<h1>Please enter your username and password</h1>
<form method="POST">
<div class="flex ph3 ph0-ns justify-center">
<div class="w-100 w-auto-ns pv3">
<h1 class="tc">Log in</h1>
<form method="POST" class="flex flex-column">
<input type="hidden" name="redirect" value="{{ .RedirectUrl }}" />
<p class="mb2">
<label class="db b" for="username">Username</label>
<input class="db w5" name="username" minlength="3" maxlength="30" type="text" required />
</p>
<p class="mb2">
<label class="db b" for="password">Password</label>
<input class="db w5" name="password" minlength="8" type="password" required />
</p>
<a href="{{ .ForgotPasswordUrl }}">Forgot your password?</a>
<p class="mt3">
<input type="submit" value="Log in" />
</p>
<div>
<label class="db b" for="username">Username</label>
<input class="db w-100 w5-ns"
name="username"
type="text"
minlength="3" maxlength="30"
required
/>
</div>
<div class="mt2">
<div class="flex justify-between">
<label class="db b" for="password">Password</label>
<a href="{{ .ForgotPasswordUrl }}" tabindex="-1">Forgot your password?</a>
</div>
<input class="db w-100 w5-ns"
name="password"
type="password"
minlength="8"
required
/>
</div>
<div class="mt3">
<input class="w-100" type="submit" value="Log in" />
</div>
<div class="tc pa3">
Need an account? <a href="{{ .RegisterUrl }}">Sign up.</a>
</div>
</form>
</div>
</div>
{{ end }}

View File

@ -1,26 +1,40 @@
{{ template "base.html" . }}
{{ define "content" }}
<div class="content-block">
<h1>Request a password reset link</h1>
<form method="POST">
<p class="mb2">
<div class="flex ph3 ph0-ns justify-center">
<div class="w-100 w-auto-ns pv3">
<h1 class="tc">Reset your password</h1>
<form method="POST" class="flex flex-column">
<div>
<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}$" />
</p>
<input class="db w-100 w5-ns"
name="username"
type="text"
minlength="3" maxlength="30"
required
/>
</div>
<p class="mb2">
<div class="mt2">
<label class="db b" for="email">Email</label>
<input class="db w5" name="email" type="text" required />
</p>
<input class="db w-100 w5-ns"
name="email"
type="text"
required
/>
</div>
<p class="mt3">
<input type="submit" value="Request password reset link" />
</p>
<div class="mt3">
<input class="w-100"
type="submit"
value="Request password reset"
/>
</div>
<p class="note mt3">
Note: To avoid your being spammed with password reset links, we limit the number of requests per account every 24 hours.
</p>
<div class="note mt3 mw-none mw5-ns">
Note: To avoid spamming you with password reset links, we limit the number of requests per account every 24 hours.
</div>
</form>
</div>
</div>
{{ end }}

View File

@ -1,9 +1,13 @@
{{ template "base.html" . }}
{{ define "content" }}
<div class="content-block">
<h4>A password reset link was sent. It'll expire in 24 hours.</h4>
<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><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>
</div>
<div class="mw6 ph3 ph0-ns pb3">
<h1>A password reset link was sent.</h1>
<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>
{{ end }}

View File

@ -1,58 +1,58 @@
{{ template "base.html" . }}
{{ define "content" }}
<div class="content-block">
<form id="register_form" method="post">
{{/* 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 */}}
<div class="flex ph3 ph0-ns justify-center">
<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 */}}
<input type="hidden" name="destination" value="{{ .DestinationURL }}" />
<p class="mb2">
<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}$" />
<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>
</p>
<div>
<label class="db b" for="username">Username</label>
<input class="db w-100 w5-ns"
name="username"
type="text"
minlength="3" maxlength="30"
pattern="^[0-9a-zA-Z][\w-]{2,29}$"
required
/>
</div>
<p class="mb2">
<label class="db b" for="displayname">Display name</label>
<input class="db w5" name="displayname" type="text" />
<span class="note db">Optional.</span>
</p>
<div class="mt2">
<label class="db" for="displayname"><b>Display name</b> <span class="c--dim i">(optional)</span></label>
<input class="db w-100 w5-ns"
name="displayname"
type="text"
/>
</div>
<p class="mb2">
<label class="db b" for="email">Email</label>
<input class="db w5" name="email" type="email" required />
</p>
<div class="mt2">
<label class="db b" for="email">Email</label>
<input class="db w-100 w5-ns"
name="email"
type="email"
required
/>
</div>
<p class="mb2">
<label class="db b" for="password">Password</label>
<input class="db w5" name="password" minlength="8" type="password" required />
</p>
<div class="mt2">
<label class="db b" for="password">Password</label>
<input class="db w-100 w5-ns"
name="password"
type="password"
minlength="8"
required
/>
</div>
{{/* TODO(asaf): Consider adding some bot-mitigation thing here */}}
<p class="mb2">
<label class="db b" for="password2">Password confirmation</label>
<input class="db w5" name="password2" minlength="8" type="password" required />
<span class="note db">Enter the same password as before, for verification.</span>
</p>
{{/* TODO(asaf): Consider adding some bot-mitigation thing here */}}
<p>
<input type="submit" value="Register" />
</p>
</form>
<script>
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 class="mt3">
<input class="w-100" type="submit" value="Register" />
</div>
</form>
</div>
</div>
{{ end }}

View File

@ -1,11 +1,16 @@
{{ template "base.html" . }}
{{ define "content" }}
<div class="content-block">
<h1>Hi! You're almost done signing up.</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>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 />
you should feel free to <a href="{{ .ContactUsUrl }}">contact the staff</a> and ask us to activate you manually.<br />
You'll want to tell us the username you chose and preferably email us from the same address you used to sign up.</p>
<div class="mw7 ph3 ph0-ns">
<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>
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>
{{ end }}

View File

@ -1,12 +1,24 @@
<p>Hello, {{ .Name }}</p>
<p>Someone has requested a password reset for your account.<br />
We hope it was you. If you didn't request it, we apologise. Kindly ignore this message.</p>
<p>To finish the password reset, visit: <a href="{{ .DoPasswordResetUrl }}"><b>{{ .DoPasswordResetUrl }}</b></a><br />
This link will be valid for 24 hours (until {{ absolutedate .Expiration }} UTC)</p>
<p>Thanks,<br />
The Handmade Network staff.</p>
<p>
Hello {{ .Name }},
</p>
<p>
Someone has requested a password reset for your Handmade Network account. To finish the password reset, visit the following link:
</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 />
<p style="font-size:small; -webkit-text-size-adjust:none ;color: #666">
You are receiving this email because someone requested a password reset for your account and supplied both your username and email address correctly.
<p style="font-size:small; -webkit-text-size-adjust:none; color: #666">
You are receiving this email because someone requested a password reset for your account and supplied both your username and email address correctly.
</p>

View File

@ -1,11 +1,16 @@
<p>Hello, {{ .Name }}</p>
<p>Welcome to <a href="{{ .HomepageUrl }}"><b>Handmade Network</b></a>.<br />
To complete the registration process, please use the following link: <a href="{{ .CompleteRegistrationUrl }}"><b>{{ .CompleteRegistrationUrl }}</b></a>.</p>
<p>
Hello {{ .Name }} - welcome to <a href="{{ .HomepageUrl }}"><b>Handmade Network</b></a>!
</p>
<p>
To complete the registration process, please use the following link:
</p>
<p>
<a href="{{ .CompleteRegistrationUrl }}">{{ .CompleteRegistrationUrl }}</a>.
</p>
<p>Thanks,<br />
The Handmade Network staff.</p>
<hr />
<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 />
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 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>. 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>

View File

@ -5,8 +5,9 @@
{{ $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 }}
{{/* 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 }}
{{ $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 }}
{{ $linkHoverColor := eq .Theme "dark" | ternary (lightness 0.65 $c) (lightness 0.45 $c) | color2css }}
@ -18,6 +19,7 @@
--theme-color-dimmest: {{ $themeDimmest }};
--theme-color-dark: {{ $themeDark }};
--theme-color-light: {{ $themeLight }};
--link-color: {{ $linkColor }};
--link-hover-color: {{ $linkHoverColor }};
@ -89,6 +91,11 @@ a:hover, button:hover, .button:hover, input[type=button]:hover {
background-color: var(--theme-color-dark);
}
.bg-theme-light {
background-color: {{ $themeLight }};
background-color: var(--theme-color-light);
}
.b--theme {
border-color: {{ $c | color2css }};
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);
}
.b--theme-light {
border-color: {{ $themeLight }};
border-color: var(--theme-color-light);
}
:root {
--background-even-background: {{ .PostBgColor }};
}

View File

@ -90,19 +90,13 @@
<div class="edit-form-row">
<div class="pt-input-ns">New password:</div>
<div>
<input id="id_new_password1" name="new_password1" type="password" />
<input name="new_password" type="password" />
<div class="c--dim f7 mw6">
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>.
</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></div>
<div>

View File

@ -9,6 +9,7 @@ import (
"time"
"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/email"
"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 {
templates.BaseData
RedirectUrl string
RegisterUrl string
ForgotPasswordUrl string
}
@ -33,8 +35,9 @@ func LoginPage(c *RequestContext) ResponseData {
var res ResponseData
res.MustWriteTemplate("auth_login.html", LoginPageData{
BaseData: getBaseDataAutocrumb(c, "Log in"),
BaseData: getBaseData(c, "Log in", nil),
RedirectUrl: c.Req.URL.Query().Get("redirect"),
RegisterUrl: hmnurl.BuildRegister(),
ForgotPasswordUrl: hmnurl.BuildRequestPasswordReset(),
}, c.Perf)
return res
@ -47,12 +50,14 @@ func Login(c *RequestContext) ResponseData {
}
redirect := form.Get("redirect")
if redirect == "" {
redirect = "/"
destination := hmnurl.BuildHomepage()
if redirect != "" && urlIsLocal(redirect) {
destination = redirect
}
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))
return res
}
@ -65,7 +70,7 @@ func Login(c *RequestContext) ResponseData {
showLoginWithFailure := func(c *RequestContext, redirect string) ResponseData {
var res ResponseData
baseData := getBaseDataAutocrumb(c, "Log in")
baseData := getBaseData(c, "Log in", nil)
baseData.AddImmediateNotice("failure", "Incorrect username or password")
res.MustWriteTemplate("auth_login.html", LoginPageData{
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.")
}
res := c.Redirect(redirect, http.StatusSeeOther)
res := c.Redirect(destination, http.StatusSeeOther)
err = loginUser(c, user, &res)
if err != nil {
return c.ErrorResponse(http.StatusInternalServerError, err)
@ -114,12 +119,14 @@ func Login(c *RequestContext) ResponseData {
}
func Logout(c *RequestContext) ResponseData {
redir := c.Req.URL.Query().Get("redirect")
if redir == "" {
redir = "/"
redirect := c.Req.URL.Query().Get("redirect")
destination := hmnurl.BuildHomepage()
if redirect != "" && urlIsLocal(redirect) {
destination = redirect
}
res := c.Redirect(redir, http.StatusSeeOther)
res := c.Redirect(destination, http.StatusSeeOther)
logoutUser(c, &res)
return res
}
@ -128,9 +135,21 @@ func RegisterNewUser(c *RequestContext) ResponseData {
if c.CurrentUser != nil {
c.Redirect(hmnurl.BuildUserSettings(c.CurrentUser.Username), http.StatusSeeOther)
}
// 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
res.MustWriteTemplate("auth_register.html", getBaseDataAutocrumb(c, "Register"), c.Perf)
res.MustWriteTemplate("auth_register.html", tmpl, c.Perf)
return res
}
@ -144,7 +163,7 @@ func RegisterNewUserSubmit(c *RequestContext) ResponseData {
displayName := strings.TrimSpace(c.Req.Form.Get("displayname"))
emailAddress := strings.TrimSpace(c.Req.Form.Get("email"))
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)) {
return c.RejectRequest("Invalid username")
}
@ -154,9 +173,6 @@ func RegisterNewUserSubmit(c *RequestContext) ResponseData {
if len(password) < 8 {
return c.RejectRequest("Password too short")
}
if password != password2 {
return c.RejectRequest("Password confirmation doesn't match password")
}
c.Perf.StartBlock("SQL", "Check blacklist")
// TODO(asaf): Check email against blacklist
@ -257,7 +273,14 @@ func RegisterNewUserSubmit(c *RequestContext) ResponseData {
if mailName == "" {
mailName = username
}
err = email.SendRegistrationEmail(emailAddress, mailName, username, ott, c.Perf)
err = email.SendRegistrationEmail(
emailAddress,
mailName,
username,
ott,
destination,
c.Perf,
)
if err != nil {
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
res.MustWriteTemplate("auth_register_success.html", RegisterNewUserSuccessData{
BaseData: getBaseDataAutocrumb(c, "Register"),
BaseData: getBaseData(c, "Register", nil),
ContactUsUrl: hmnurl.BuildContactPage(),
}, c.Perf)
return res
@ -291,8 +314,9 @@ func RegisterNewUserSuccess(c *RequestContext) ResponseData {
type EmailValidationData struct {
templates.BaseData
Token string
Username string
Token string
Username string
DestinationURL string
}
func EmailConfirmation(c *RequestContext) ResponseData {
@ -329,9 +353,10 @@ func EmailConfirmation(c *RequestContext) ResponseData {
var res ResponseData
res.MustWriteTemplate("auth_email_validation.html", EmailValidationData{
BaseData: getBaseDataAutocrumb(c, "Register"),
Token: token,
Username: username,
BaseData: getBaseData(c, "Register", nil),
Token: token,
Username: username,
DestinationURL: c.Req.URL.Query().Get("destination"),
}, c.Perf)
return res
}
@ -342,6 +367,7 @@ func EmailConfirmationSubmit(c *RequestContext) ResponseData {
token := c.Req.Form.Get("token")
username := c.Req.Form.Get("username")
password := c.Req.Form.Get("password")
destination := c.Req.Form.Get("destination")
validationResult := validateUsernameAndToken(c, username, token, models.TokenTypeRegistration)
if !validationResult.Match {
@ -354,7 +380,7 @@ func EmailConfirmationSubmit(c *RequestContext) ResponseData {
return c.ErrorResponse(http.StatusInternalServerError, err)
} else if !success {
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.
baseData.AddImmediateNotice("failure", "Incorrect password. Please try again.")
res.MustWriteTemplate("auth_email_validation.html", EmailValidationData{
@ -401,7 +427,12 @@ func EmailConfirmationSubmit(c *RequestContext) ResponseData {
}
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!")
err = loginUser(c, validationResult.User, &res)
if err != nil {
@ -432,7 +463,7 @@ func RequestPasswordReset(c *RequestContext) ResponseData {
return c.Redirect(hmnurl.BuildHomepage(), http.StatusSeeOther)
}
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
}
@ -550,7 +581,7 @@ func PasswordResetSent(c *RequestContext) ResponseData {
}
var res ResponseData
res.MustWriteTemplate("auth_password_reset_sent.html", PasswordResetSentData{
BaseData: getBaseDataAutocrumb(c, "Password Reset"),
BaseData: getBaseData(c, "Password Reset", nil),
ContactUsUrl: hmnurl.BuildContactPage(),
}, c.Perf)
return res
@ -584,7 +615,7 @@ func DoPasswordReset(c *RequestContext) ResponseData {
}
res.MustWriteTemplate("auth_do_password_reset.html", DoPasswordResetData{
BaseData: getBaseDataAutocrumb(c, "Password Reset"),
BaseData: getBaseData(c, "Password Reset", nil),
Username: username,
Token: token,
}, c.Perf)
@ -597,7 +628,6 @@ func DoPasswordResetSubmit(c *RequestContext) ResponseData {
token := c.Req.Form.Get("token")
username := c.Req.Form.Get("username")
password := c.Req.Form.Get("password")
password2 := c.Req.Form.Get("password2")
validationResult := validateUsernameAndToken(c, username, token, models.TokenTypePasswordReset)
if !validationResult.Match {
@ -611,9 +641,6 @@ func DoPasswordResetSubmit(c *RequestContext) ResponseData {
if len(password) < 8 {
return c.RejectRequest("Password too short")
}
if password != password2 {
return c.RejectRequest("Password confirmation doesn't match password")
}
hashed := auth.HashPassword(password)
@ -806,3 +833,7 @@ func validateUsernameAndToken(c *RequestContext, username string, token string,
return result
}
func urlIsLocal(url string) bool {
return strings.HasPrefix(url, config.Config.BaseUrl)
}

View File

@ -405,10 +405,9 @@ func UserSettingsSave(c *RequestContext) ResponseData {
// Update password
oldPassword := form.Get("old_password")
newPassword := form.Get("new_password1")
newPasswordConfirmation := form.Get("new_password2")
newPassword := form.Get("new_password")
if oldPassword != "" && newPassword != "" {
errorRes := updatePassword(c, tx, oldPassword, newPassword, newPasswordConfirmation)
errorRes := updatePassword(c, tx, oldPassword, newPassword)
if errorRes != nil {
return *errorRes
}
@ -526,12 +525,7 @@ func UserProfileAdminNuke(c *RequestContext) ResponseData {
return res
}
func updatePassword(c *RequestContext, tx pgx.Tx, old, new, confirm string) *ResponseData {
if new != confirm {
res := c.RejectRequest("Your password and password confirmation did not match.")
return &res
}
func updatePassword(c *RequestContext, tx pgx.Tx, old, new string) *ResponseData {
oldHashedPassword, err := auth.ParsePasswordString(c.CurrentUser.Password)
if err != nil {
c.Logger.Warn().Err(err).Msg("failed to parse user's password string")