first-commit
This commit is contained in:
40
templates/user/auth/activate.tmpl
Normal file
40
templates/user/auth/activate.tmpl
Normal file
@@ -0,0 +1,40 @@
|
||||
{{template "base/head" .}}
|
||||
<div role="main" aria-label="{{.Title}}" class="page-content user activate">
|
||||
<div class="ui middle very relaxed page grid">
|
||||
<div class="column">
|
||||
<form class="ui form ignore-dirty tw-max-w-2xl tw-m-auto" action="{{AppSubUrl}}/user/activate" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<h2 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "auth.active_your_account"}}
|
||||
</h2>
|
||||
<div class="ui attached segment">
|
||||
{{template "base/alert" .}}
|
||||
{{if .NeedVerifyLocalPassword}}
|
||||
<div class="required field">
|
||||
<label for="verify-password">{{ctx.Locale.Tr "password"}}</label>
|
||||
<input id="verify-password" name="password" type="password" autocomplete="off" required>
|
||||
</div>
|
||||
<div class="inline field">
|
||||
<button class="ui primary button">{{ctx.Locale.Tr "install.confirm_password"}}</button>
|
||||
</div>
|
||||
<input name="code" type="hidden" value="{{.ActivationCode}}">
|
||||
{{else}}
|
||||
<p>{{ctx.Locale.Tr "auth.has_unconfirmed_mail" .SignedUser.Name .SignedUser.Email}}</p>
|
||||
<details>
|
||||
<summary>{{ctx.Locale.Tr "auth.change_unconfirmed_mail_address"}}</summary>
|
||||
<div class="tw-py-2">
|
||||
<label for="change-email">{{ctx.Locale.Tr "email"}}</label>
|
||||
<input id="change-email" name="change_email" type="email" value="{{.SignedUser.Email}}">
|
||||
</div>
|
||||
</details>
|
||||
<div class="divider"></div>
|
||||
<div class="text">
|
||||
<button class="ui primary button">{{ctx.Locale.Tr "auth.resend_mail"}}</button>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
15
templates/user/auth/activate_prompt.tmpl
Normal file
15
templates/user/auth/activate_prompt.tmpl
Normal file
@@ -0,0 +1,15 @@
|
||||
{{template "base/head" .}}
|
||||
<div role="main" aria-label="{{.Title}}" class="page-content user activate">
|
||||
<div class="ui middle very relaxed page grid">
|
||||
<div class="column">
|
||||
<h2 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "auth.active_your_account"}}
|
||||
</h2>
|
||||
<div class="ui attached segment">
|
||||
{{template "base/alert" .}}
|
||||
<p>{{.ActivationPromptMessage}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
29
templates/user/auth/captcha.tmpl
Normal file
29
templates/user/auth/captcha.tmpl
Normal file
@@ -0,0 +1,29 @@
|
||||
{{if .EnableCaptcha}}{{if eq .CaptchaType "image"}}
|
||||
<div class="inline field tw-text-center">
|
||||
{{.Captcha.CreateHTML}}
|
||||
</div>
|
||||
<div class="required field {{if .Err_Captcha}}error{{end}}">
|
||||
<label for="captcha">{{ctx.Locale.Tr "captcha"}}</label>
|
||||
<input id="captcha" name="captcha" value="{{.captcha}}" autocomplete="off">
|
||||
</div>
|
||||
{{else if eq .CaptchaType "recaptcha"}}
|
||||
<div class="inline field tw-text-center required">
|
||||
<div id="captcha" data-captcha-type="g-recaptcha" class="g-recaptcha-style" data-sitekey="{{.RecaptchaSitekey}}"></div>
|
||||
</div>
|
||||
<script defer src='{{URLJoin .RecaptchaURL "api.js"}}'></script>
|
||||
{{else if eq .CaptchaType "hcaptcha"}}
|
||||
<div class="inline field tw-text-center required">
|
||||
<div id="captcha" data-captcha-type="h-captcha" class="h-captcha-style" data-sitekey="{{.HcaptchaSitekey}}"></div>
|
||||
</div>
|
||||
<script defer src='https://hcaptcha.com/1/api.js'></script>
|
||||
{{else if eq .CaptchaType "mcaptcha"}}
|
||||
<div class="inline field tw-text-center">
|
||||
<div class="m-captcha-style" id="mcaptcha__widget-container"></div>
|
||||
<div id="captcha" data-captcha-type="m-captcha" data-sitekey="{{.McaptchaSitekey}}" data-instance-url="{{.McaptchaURL}}"></div>
|
||||
</div>
|
||||
{{else if eq .CaptchaType "cfturnstile"}}
|
||||
<div class="inline field tw-text-center">
|
||||
<div id="captcha" data-captcha-type="cf-turnstile" data-sitekey="{{.CfTurnstileSitekey}}"></div>
|
||||
</div>
|
||||
<script defer src='https://challenges.cloudflare.com/turnstile/v0/api.js'></script>
|
||||
{{end}}{{end}}
|
7
templates/user/auth/change_passwd.tmpl
Normal file
7
templates/user/auth/change_passwd.tmpl
Normal file
@@ -0,0 +1,7 @@
|
||||
{{template "base/head" .}}
|
||||
<div role="main" aria-label="{{.Title}}" class="page-content user signin{{if .LinkAccountMode}} icon{{end}}">
|
||||
<div class="ui container">
|
||||
{{template "user/auth/change_passwd_inner" .}}
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
22
templates/user/auth/change_passwd_inner.tmpl
Normal file
22
templates/user/auth/change_passwd_inner.tmpl
Normal file
@@ -0,0 +1,22 @@
|
||||
{{if or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn)}}
|
||||
{{template "base/alert" .}}
|
||||
{{end}}
|
||||
<h4 class="ui top attached header center">
|
||||
{{ctx.Locale.Tr "settings.change_password"}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
<form class="ui form tw-max-w-2xl tw-m-auto" action="{{.ChangePasscodeLink}}" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="required field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}">
|
||||
<label for="password">{{ctx.Locale.Tr "password"}}</label>
|
||||
<input id="password" name="password" type="password" value="{{.password}}" autocomplete="new-password" required>
|
||||
</div>
|
||||
<div class="required field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister))}}error{{end}}">
|
||||
<label for="retype">{{ctx.Locale.Tr "re_type"}}</label>
|
||||
<input id="retype" name="retype" type="password" autocomplete="new-password" required>
|
||||
</div>
|
||||
<div class="inline field">
|
||||
<button class="ui primary button">{{ctx.Locale.Tr "settings.change_password"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
39
templates/user/auth/forgot_passwd.tmpl
Normal file
39
templates/user/auth/forgot_passwd.tmpl
Normal file
@@ -0,0 +1,39 @@
|
||||
{{template "base/head" .}}
|
||||
<div role="main" aria-label="{{.Title}}" class="page-content user forgot password">
|
||||
<div class="ui middle very relaxed page grid">
|
||||
<div class="column">
|
||||
<form class="ui form ignore-dirty" action="{{.Link}}" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<h2 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "auth.forgot_password_title"}}
|
||||
</h2>
|
||||
<div class="ui attached segment">
|
||||
{{template "base/alert" .}}
|
||||
{{if .IsResetSent}}
|
||||
<p>{{ctx.Locale.Tr "auth.reset_password_mail_sent_prompt" .Email .ResetPwdCodeLives}}</p>
|
||||
{{else if .IsResetRequest}}
|
||||
<div class="required field {{if .Err_Email}}error{{end}}">
|
||||
<label for="email">{{ctx.Locale.Tr "email"}}</label>
|
||||
<input id="email" name="email" type="email" value="{{.Email}}" autofocus required>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div class="inline field">
|
||||
<button class="ui primary button">{{ctx.Locale.Tr "auth.send_reset_mail"}}</button>
|
||||
</div>
|
||||
{{else if .IsResetDisable}}
|
||||
<p class="center">
|
||||
{{if $.IsAdmin}}
|
||||
{{ctx.Locale.Tr "auth.disable_forgot_password_mail_admin"}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "auth.disable_forgot_password_mail"}}
|
||||
{{end}}
|
||||
</p>
|
||||
{{else if .ResendLimited}}
|
||||
<p class="center">{{ctx.Locale.Tr "auth.resent_limit_prompt"}}</p>
|
||||
{{end}}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
34
templates/user/auth/grant.tmpl
Normal file
34
templates/user/auth/grant.tmpl
Normal file
@@ -0,0 +1,34 @@
|
||||
{{template "base/head" .}}
|
||||
<div role="main" aria-label="{{.Title}}" class="page-content oauth2-authorize-application-box">
|
||||
<div class="ui container tw-max-w-[500px]">
|
||||
<h3 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "auth.authorize_title" .Application.Name}}
|
||||
</h3>
|
||||
<div class="ui attached segment">
|
||||
{{template "base/alert" .}}
|
||||
<p>
|
||||
{{if not .AdditionalScopes}}
|
||||
<b>{{ctx.Locale.Tr "auth.authorize_application_description"}}</b><br>
|
||||
{{end}}
|
||||
{{ctx.Locale.Tr "auth.authorize_application_created_by" .ApplicationCreatorLinkHTML}}<br>
|
||||
{{ctx.Locale.Tr "auth.authorize_application_with_scopes" (HTMLFormat "<b>%s</b>" .Scope)}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="ui attached segment">
|
||||
<p>{{ctx.Locale.Tr "auth.authorize_redirect_notice" .ApplicationRedirectDomainHTML}}</p>
|
||||
</div>
|
||||
<div class="ui attached segment tw-text-center">
|
||||
<form method="post" action="{{AppSubUrl}}/login/oauth/grant">
|
||||
{{.CsrfTokenHtml}}
|
||||
<input type="hidden" name="client_id" value="{{.Application.ClientID}}">
|
||||
<input type="hidden" name="state" value="{{.State}}">
|
||||
<input type="hidden" name="scope" value="{{.Scope}}">
|
||||
<input type="hidden" name="nonce" value="{{.Nonce}}">
|
||||
<input type="hidden" name="redirect_uri" value="{{.RedirectURI}}">
|
||||
<button type="submit" id="authorize-app" name="granted" value="true" class="ui red inline button">{{ctx.Locale.Tr "auth.authorize_application"}}</button>
|
||||
<button type="submit" name="granted" value="false" class="ui basic primary inline button">{{ctx.Locale.Tr "cancel"}}</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
13
templates/user/auth/grant_error.tmpl
Normal file
13
templates/user/auth/grant_error.tmpl
Normal file
@@ -0,0 +1,13 @@
|
||||
{{template "base/head" .}}
|
||||
<div role="main" aria-label="{{.Title}}" class="page-content oauth2-authorize-application-box">
|
||||
<div class="ui container tw-max-w-[500px]">
|
||||
<h1 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "auth.authorization_failed"}}
|
||||
</h1>
|
||||
<h3 class="ui attached segment">{{.Error.ErrorDescription}}</h3>
|
||||
<div class="ui attached segment">
|
||||
<p>{{ctx.Locale.Tr "auth.authorization_failed_desc"}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
36
templates/user/auth/link_account.tmpl
Normal file
36
templates/user/auth/link_account.tmpl
Normal file
@@ -0,0 +1,36 @@
|
||||
{{template "base/head" .}}
|
||||
<div role="main" aria-label="{{.Title}}" class="page-content user link-account">
|
||||
<overflow-menu class="ui secondary pointing tabular top attached borderless menu secondary-nav">
|
||||
<div class="overflow-menu-items tw-justify-center">
|
||||
<!-- TODO handle .ShowRegistrationButton once other login bugs are fixed -->
|
||||
{{if not .AllowOnlyInternalRegistration}}
|
||||
<a class="item {{if not .user_exists}}active{{end}}"
|
||||
data-tab="auth-link-signup-tab">
|
||||
{{ctx.Locale.Tr "auth.oauth_signup_tab"}}
|
||||
</a>
|
||||
{{end}}
|
||||
<a class="item {{if .user_exists}}active{{end}}"
|
||||
data-tab="auth-link-signin-tab">
|
||||
{{ctx.Locale.Tr "auth.oauth_signin_tab"}}
|
||||
</a>
|
||||
</div>
|
||||
</overflow-menu>
|
||||
<div class="ui middle very relaxed page grid">
|
||||
<div class="column tw-my-5">
|
||||
{{/* these styles are quite tricky but it needs to be the same as the signin page */}}
|
||||
<div class="ui tab {{if not .user_exists}}active{{end}}" data-tab="auth-link-signup-tab">
|
||||
<div class="tw-flex tw-flex-col tw-gap-4 tw-max-w-2xl tw-m-auto">
|
||||
{{if .AutoRegistrationFailedPrompt}}<div class="ui message">{{.AutoRegistrationFailedPrompt}}</div>{{end}}
|
||||
{{template "user/auth/signup_inner" .}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="ui tab {{if .user_exists}}active{{end}}" data-tab="auth-link-signin-tab">
|
||||
<div class="tw-flex tw-flex-col tw-gap-4 tw-max-w-2xl tw-m-auto">
|
||||
{{template "user/auth/signin_inner" .}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{template "base/footer" .}}
|
24
templates/user/auth/oauth_container.tmpl
Normal file
24
templates/user/auth/oauth_container.tmpl
Normal file
@@ -0,0 +1,24 @@
|
||||
<div id="oauth2-login-navigator" class="tw-py-1">
|
||||
<div class="tw-flex tw-flex-col tw-justify-center">
|
||||
<div id="oauth2-login-navigator-inner" class="tw-flex tw-flex-col tw-flex-wrap tw-items-center tw-gap-2">
|
||||
{{range $provider := .OAuth2Providers}}
|
||||
<a class="{{$provider.Name}} ui button tw-flex tw-items-center tw-justify-center tw-py-2 tw-w-full oauth-login-link" href="{{AppSubUrl}}/user/oauth2/{{$provider.DisplayName}}">
|
||||
{{$provider.IconHTML 28}}
|
||||
{{ctx.Locale.Tr "sign_in_with_provider" $provider.DisplayName}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{if .EnableOpenIDSignIn}}
|
||||
<a class="openid ui button tw-flex tw-items-center tw-justify-center tw-py-2 tw-w-full" href="{{AppSubUrl}}/user/login/openid">
|
||||
{{svg "fontawesome-openid" 28 "tw-mr-2"}}
|
||||
{{ctx.Locale.Tr "sign_in_with_provider" "OpenID"}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{if .EnableSSPI}}
|
||||
<a class="ui button tw-flex tw-items-center tw-justify-center tw-py-2 tw-w-full" rel="nofollow" href="{{AppSubUrl}}/user/login?auth_with_sspi=1">
|
||||
{{svg "fontawesome-windows"}}
|
||||
SSPI
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
49
templates/user/auth/oidc_wellknown.tmpl
Normal file
49
templates/user/auth/oidc_wellknown.tmpl
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"issuer": "{{.OidcIssuer}}",
|
||||
"authorization_endpoint": "{{.OidcBaseUrl}}/login/oauth/authorize",
|
||||
"token_endpoint": "{{.OidcBaseUrl}}/login/oauth/access_token",
|
||||
"jwks_uri": "{{.OidcBaseUrl}}/login/oauth/keys",
|
||||
"userinfo_endpoint": "{{.OidcBaseUrl}}/login/oauth/userinfo",
|
||||
"introspection_endpoint": "{{.OidcBaseUrl}}/login/oauth/introspect",
|
||||
"response_types_supported": [
|
||||
"code",
|
||||
"id_token"
|
||||
],
|
||||
"id_token_signing_alg_values_supported": [
|
||||
"{{.SigningKeyMethodAlg}}"
|
||||
],
|
||||
"subject_types_supported": [
|
||||
"public"
|
||||
],
|
||||
"scopes_supported": [
|
||||
"openid",
|
||||
"profile",
|
||||
"email",
|
||||
"groups"
|
||||
],
|
||||
"claims_supported": [
|
||||
"aud",
|
||||
"exp",
|
||||
"iat",
|
||||
"iss",
|
||||
"sub",
|
||||
"name",
|
||||
"preferred_username",
|
||||
"profile",
|
||||
"picture",
|
||||
"website",
|
||||
"locale",
|
||||
"updated_at",
|
||||
"email",
|
||||
"email_verified",
|
||||
"groups"
|
||||
],
|
||||
"code_challenge_methods_supported": [
|
||||
"plain",
|
||||
"S256"
|
||||
],
|
||||
"grant_types_supported": [
|
||||
"authorization_code",
|
||||
"refresh_token"
|
||||
]
|
||||
}
|
16
templates/user/auth/prohibit_login.tmpl
Normal file
16
templates/user/auth/prohibit_login.tmpl
Normal file
@@ -0,0 +1,16 @@
|
||||
{{template "base/head" .}}
|
||||
<div role="main" aria-label="{{.Title}}" class="page-content user activate">
|
||||
<div class="ui middle very relaxed page grid">
|
||||
<div class="column">
|
||||
<form class="ui form tw-max-w-2xl tw-m-auto">
|
||||
<h2 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "auth.prohibit_login"}}
|
||||
</h2>
|
||||
<div class="ui attached segment">
|
||||
<p>{{ctx.Locale.Tr "auth.prohibit_login_desc"}}</p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
65
templates/user/auth/reset_passwd.tmpl
Normal file
65
templates/user/auth/reset_passwd.tmpl
Normal file
@@ -0,0 +1,65 @@
|
||||
{{template "base/head" .}}
|
||||
<div role="main" aria-label="{{.Title}}" class="page-content user reset password">
|
||||
<div class="ui middle very relaxed page grid">
|
||||
<div class="column">
|
||||
<form class="ui form ignore-dirty" action="{{.Link}}" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<input name="code" type="hidden" value="{{.Code}}">
|
||||
<h2 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "auth.reset_password"}}
|
||||
</h2>
|
||||
<div class="ui attached segment">
|
||||
{{template "base/alert" .}}
|
||||
{{if .user_email}}
|
||||
<div class="inline field">
|
||||
<label for="user_name">{{ctx.Locale.Tr "email"}}</label>
|
||||
<input id="user_name" type="text" value="{{.user_email}}" disabled>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .IsResetForm}}
|
||||
<div class="required field {{if .Err_Password}}error{{end}}">
|
||||
<label for="password">{{ctx.Locale.Tr "settings.new_password"}}</label>
|
||||
<input id="password" name="password" type="password" value="{{.password}}" autocomplete="new-password" autofocus required>
|
||||
</div>
|
||||
{{if not .user_signed_in}}
|
||||
<div class="inline field">
|
||||
<div class="ui checkbox">
|
||||
<label>{{ctx.Locale.Tr "auth.remember_me"}}</label>
|
||||
<input name="remember" type="checkbox">
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .has_two_factor}}
|
||||
<h4 class="ui dividing header">
|
||||
{{ctx.Locale.Tr "twofa"}}
|
||||
</h4>
|
||||
<div class="ui warning visible message">{{ctx.Locale.Tr "settings.twofa_is_enrolled"}}</div>
|
||||
{{if .scratch_code}}
|
||||
<div class="required inline field {{if .Err_Token}}error{{end}}">
|
||||
<label for="token">{{ctx.Locale.Tr "auth.scratch_code"}}</label>
|
||||
<input id="token" name="token" type="text" autocomplete="off" autofocus required>
|
||||
</div>
|
||||
<input type="hidden" name="scratch_code" value="true">
|
||||
{{else}}
|
||||
<div class="required field {{if .Err_Passcode}}error{{end}}">
|
||||
<label for="passcode">{{ctx.Locale.Tr "passcode"}}</label>
|
||||
<input id="passcode" name="passcode" type="number" autocomplete="off" autofocus required>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
<div class="divider"></div>
|
||||
<div class="inline field">
|
||||
<button class="ui primary button">{{ctx.Locale.Tr "auth.reset_password_helper"}}</button>
|
||||
{{if and .has_two_factor (not .scratch_code)}}
|
||||
<a href="?code={{.Code}}&scratch_code=true">{{ctx.Locale.Tr "auth.use_scratch_code"}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
{{else}}
|
||||
<p class="center">{{ctx.Locale.Tr "auth.invalid_code_forgot_password" (printf "%s/user/forgot_password" AppSubUrl)}}</p>
|
||||
{{end}}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
11
templates/user/auth/signin.tmpl
Normal file
11
templates/user/auth/signin.tmpl
Normal file
@@ -0,0 +1,11 @@
|
||||
{{template "base/head" .}}
|
||||
<div role="main" aria-label="{{.Title}}" class="page-content user signin{{if .LinkAccountMode}} icon{{end}}">
|
||||
{{template "user/auth/signin_navbar" .}}
|
||||
<div class="ui middle very relaxed page grid">
|
||||
{{/* these styles are quite tricky and should also apply to the signup and link_account pages */}}
|
||||
<div class="column tw-flex tw-flex-col tw-gap-4 tw-max-w-2xl tw-m-auto">
|
||||
{{template "user/auth/signin_inner" .}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
78
templates/user/auth/signin_inner.tmpl
Normal file
78
templates/user/auth/signin_inner.tmpl
Normal file
@@ -0,0 +1,78 @@
|
||||
<div class="ui container fluid">
|
||||
{{if or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn)}}
|
||||
{{template "base/alert" .}}
|
||||
{{end}}
|
||||
<h4 class="ui top attached header center">
|
||||
{{if .LinkAccountMode}}
|
||||
{{ctx.Locale.Tr "auth.oauth_signin_title"}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "auth.login_userpass"}}
|
||||
{{end}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
{{if .EnablePasswordSignInForm}}
|
||||
<form class="ui form" action="{{.SignInLink}}" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="required field {{if and (.Err_UserName) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}">
|
||||
<label for="user_name">{{ctx.Locale.Tr "home.uname_holder"}}</label>
|
||||
<input id="user_name" type="text" name="user_name" value="{{.user_name}}" autofocus required tabindex="1">
|
||||
</div>
|
||||
{{if or (not .DisablePassword) .LinkAccountMode}}
|
||||
<div class="required field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}">
|
||||
<div class="tw-flex tw-mb-1">
|
||||
<label for="password" class="tw-flex-1">{{ctx.Locale.Tr "password"}}</label>
|
||||
<a href="{{AppSubUrl}}/user/forgot_password" tabindex="4">{{ctx.Locale.Tr "auth.forgot_password"}}</a>
|
||||
</div>
|
||||
<input id="password" name="password" type="password" value="{{.password}}" autocomplete="current-password" required tabindex="2">
|
||||
</div>
|
||||
{{end}}
|
||||
{{if not .LinkAccountMode}}
|
||||
<div class="inline field">
|
||||
<div class="ui checkbox">
|
||||
<label>{{ctx.Locale.Tr "auth.remember_me"}}</label>
|
||||
<input name="remember" type="checkbox" tabindex="5">
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{template "user/auth/captcha" .}}
|
||||
|
||||
<div class="field">
|
||||
<button class="ui primary button tw-w-full" tabindex="3">
|
||||
{{if .LinkAccountMode}}
|
||||
{{ctx.Locale.Tr "auth.oauth_signin_submit"}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "sign_in"}}
|
||||
{{end}}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{{end}}{{/*if .EnablePasswordSignInForm*/}}
|
||||
{{/* "oauth_container" contains not only "oauth2" methods, but also "OIDC" and "SSPI" methods */}}
|
||||
{{$showOAuth2Methods := or .OAuth2Providers .EnableOpenIDSignIn .EnableSSPI}}
|
||||
{{if and $showOAuth2Methods .EnablePasswordSignInForm}}
|
||||
<div class="divider divider-text">{{ctx.Locale.Tr "sign_in_or"}}</div>
|
||||
{{end}}
|
||||
{{if $showOAuth2Methods}}
|
||||
{{template "user/auth/oauth_container" .}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if or .EnablePasskeyAuth .ShowRegistrationButton}}
|
||||
<div class="ui container fluid">
|
||||
<div class="ui attached segment header top tw-max-w-2xl tw-m-auto tw-flex tw-flex-col tw-items-center">
|
||||
{{if .EnablePasskeyAuth}}
|
||||
{{template "user/auth/webauthn_error" .}}
|
||||
<a class="signin-passkey">{{ctx.Locale.Tr "auth.signin_passkey"}}</a>
|
||||
{{end}}
|
||||
|
||||
{{if .ShowRegistrationButton}}
|
||||
<div class="field">
|
||||
<span>{{ctx.Locale.Tr "auth.need_account"}}</span>
|
||||
<a href="{{AppSubUrl}}/user/sign_up">{{ctx.Locale.Tr "auth.sign_up_now"}}</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
29
templates/user/auth/signin_navbar.tmpl
Normal file
29
templates/user/auth/signin_navbar.tmpl
Normal file
@@ -0,0 +1,29 @@
|
||||
{{if or .EnableOpenIDSignIn .EnableSSPI .EnableWechatQRSignIn}}
|
||||
<overflow-menu class="ui secondary pointing tabular top attached borderless menu navbar secondary-nav">
|
||||
<div class="overflow-menu-items tw-justify-center">
|
||||
<a class="{{if .PageIsSignIn}}active {{end}}item" rel="nofollow" href="{{AppSubUrl}}/user/login">
|
||||
{{ctx.Locale.Tr "auth.login_userpass"}}
|
||||
</a>
|
||||
<a class="{{if .PageIsSignUp}}active{{end}} item" rel="nofollow" href="{{AppSubUrl}}/user/sign_up">
|
||||
{{ctx.Locale.Tr "auth.create_new_account"}}
|
||||
</a>
|
||||
{{if .EnableWechatQRSignIn}}
|
||||
<a class="{{if .PageIsWechatQrLogin}}active {{end}} item" rel="nofollow" href="{{AppSubUrl}}/user/login/wechat">
|
||||
{{ctx.Locale.Tr "settings.wechat_qr_login"}}
|
||||
</a>
|
||||
{{end}}
|
||||
{{if .EnableOpenIDSignIn}}
|
||||
<a class="{{if .PageIsLoginOpenID}}active {{end}}item" rel="nofollow" href="{{AppSubUrl}}/user/login/openid">
|
||||
{{svg "fontawesome-openid"}}
|
||||
OpenID
|
||||
</a>
|
||||
{{end}}
|
||||
{{if .EnableSSPI}}
|
||||
<a class="item" rel="nofollow" href="{{AppSubUrl}}/user/login?auth_with_sspi=1">
|
||||
{{svg "fontawesome-windows"}}
|
||||
SSPI
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</overflow-menu>
|
||||
{{end}}
|
51
templates/user/auth/signin_openid.tmpl
Normal file
51
templates/user/auth/signin_openid.tmpl
Normal file
@@ -0,0 +1,51 @@
|
||||
{{template "base/head" .}}
|
||||
<div role="main" aria-label="{{.Title}}" class="page-content user signin openid">
|
||||
<div class="ui middle very relaxed page grid">
|
||||
<div class="column tw-flex tw-flex-col tw-gap-4 tw-max-w-2xl tw-m-auto">
|
||||
<a href="{{AppSubUrl}}/user/login" class="tw-mx-auto">
|
||||
<img width="100" height="100" src="{{AssetUrlPrefix}}/img/logo.svg" alt="{{ctx.Locale.Tr "logo"}}">
|
||||
</a>
|
||||
|
||||
<div class="ui container fluid">
|
||||
{{template "base/alert" .}}
|
||||
<h4 class="ui top attached header center">
|
||||
{{svg "fontawesome-openid"}}
|
||||
OpenID
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
<form class="ui form tw-m-auto" action="{{.Link}}" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="inline field">
|
||||
{{ctx.Locale.Tr "auth.openid_signin_desc"}}
|
||||
</div>
|
||||
<div class="required field {{if .Err_OpenID}}error{{end}}">
|
||||
<label for="openid">
|
||||
{{svg "fontawesome-openid"}}
|
||||
OpenID URI
|
||||
</label>
|
||||
<input id="openid" name="openid" value="{{.openid}}" autofocus required>
|
||||
</div>
|
||||
<div class="inline field">
|
||||
<div class="ui checkbox">
|
||||
<label>{{ctx.Locale.Tr "auth.remember_me"}}</label>
|
||||
<input name="remember" type="checkbox">
|
||||
</div>
|
||||
</div>
|
||||
<div class="inline field">
|
||||
<button class="ui primary button tw-w-full">{{ctx.Locale.Tr "sign_in"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui container fluid">
|
||||
{{template "user/auth/webauthn_error" .}}
|
||||
|
||||
<div class="ui attached segment header top tw-flex tw-flex-col tw-items-center">
|
||||
<a href="{{AppSubUrl}}/user/login">{{ctx.Locale.Tr "auth.back_to_sign_in"}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
12
templates/user/auth/signin_sms.tmpl
Normal file
12
templates/user/auth/signin_sms.tmpl
Normal file
@@ -0,0 +1,12 @@
|
||||
{{template "base/head" .}}
|
||||
<div role="main" aria-label="{{.Title}}" class="page-content user signin{{if .LinkAccountMode}} icon{{end}}">
|
||||
{{template "user/auth/signin_navbar" .}}
|
||||
|
||||
{{/* 短信登录页面表单项容器 */}}
|
||||
<div class="ui middle very relaxed page grid">
|
||||
<div class="ui container column fluid">
|
||||
{{template "user/auth/signin_sms_inner" .}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
37
templates/user/auth/signin_sms_inner.tmpl
Normal file
37
templates/user/auth/signin_sms_inner.tmpl
Normal file
@@ -0,0 +1,37 @@
|
||||
<h4 class="ui top attached header center">
|
||||
{{ctx.Locale.Tr "register_or_sign_in_with_provider" (ctx.Locale.Tr "settings.phone_sms_code")}}
|
||||
</h4>
|
||||
|
||||
<div class="ui attached segment">
|
||||
<form class="ui form tw-max-w-2xl tw-m-auto" action="{{.SignInSmsLink}}" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="required field {{if and (.Err_UserName) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn))}}error{{end}}">
|
||||
<label for="user_name">{{ctx.Locale.Tr "settings.phone_number"}}</label>
|
||||
<input id="user_name" type="text" name="user_name" value="{{.user_name}}" autofocus required>
|
||||
</div>
|
||||
|
||||
{{template "user/auth/captcha" .}}
|
||||
|
||||
<div class="required field">
|
||||
<label for="smsCode">{{ctx.Locale.Tr "settings.phone_sms_code"}}</label>
|
||||
<input id="smsCode" name="smsCode" type="text" value="{{.smsCode}}" required>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="inline field">
|
||||
<button class="ui primary button" id="sendSms" name="sendSms">{{ctx.Locale.Tr "settings.phone_sms_send"}}</button>
|
||||
<button class="ui primary button" id="loginViaSms">{{ctx.Locale.Tr "sign_in"}}</button>
|
||||
</div>
|
||||
|
||||
|
||||
{{if not .LinkAccountMode}}
|
||||
<div class="inline field">
|
||||
<div class="ui checkbox">
|
||||
<label>{{ctx.Locale.Tr "auth.remember_me"}}</label>
|
||||
<input name="remember" type="checkbox">
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
</form>
|
||||
</div>
|
12
templates/user/auth/signin_wechat_qr.tmpl
Normal file
12
templates/user/auth/signin_wechat_qr.tmpl
Normal file
@@ -0,0 +1,12 @@
|
||||
{{template "base/head" .}}
|
||||
<div role="main" aria-label="{{.Title}}" class="page-content user signin{{if .LinkAccountMode}} icon{{end}}">
|
||||
{{template "user/auth/signin_navbar" .}}
|
||||
|
||||
{{/* 登录页面表单项容器 */}}
|
||||
<div class="ui middle very relaxed page grid">
|
||||
<div class="ui container column fluid">
|
||||
{{template "user/auth/signin_wechat_qr_inner" .}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
207
templates/user/auth/signin_wechat_qr_inner.tmpl
Normal file
207
templates/user/auth/signin_wechat_qr_inner.tmpl
Normal file
@@ -0,0 +1,207 @@
|
||||
{{/* 引入微信公众号二维码扫码注册、登录错误信息一次性提示信息 ctx.Flash.Error() 显示容器 */}}
|
||||
{{template "base/alert" .}}
|
||||
|
||||
|
||||
{{if .wechatQRScanSuccess}}
|
||||
<!-- 扫码成功只做提示 -->
|
||||
<div class="ui info message">
|
||||
<p class="text center">{{ctx.Locale.Tr "settings.wechat_update_success"}}</p>
|
||||
</div>
|
||||
|
||||
{{else}}
|
||||
|
||||
{{/* ============================================================= 扫码失败,需要扫码 - 开始 ============================================================= */}}
|
||||
{{if .PageIsSignIn}}
|
||||
<h4 class="ui top attached header center">
|
||||
{{ctx.Locale.Tr "settings.wechat_qr_prompt"}}
|
||||
</h4>
|
||||
{{end}}
|
||||
|
||||
<div class="ui attached segment">
|
||||
<form class="ui form tw-max-w-2xl tw-m-auto" method="post">
|
||||
<div class="wechat-qr-container">
|
||||
<img id="idWechatQr" class="wechat-qr-image" src="{{.wechatQrCodeUrl}}" alt="Wechat Official Accout QR Code Ticket {{.wechatQrTicket}}" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
.wechat-qr-container {
|
||||
text-align: center; /* 将文本内容居中 */
|
||||
position: relative; /* 添加相对定位 */
|
||||
}
|
||||
|
||||
.wechat-qr-container {
|
||||
text-align: center;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.wechat-qr-image {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 50%;
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
.expire-mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.7); /* 半透明黑色遮罩 */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 150%;
|
||||
color: white;
|
||||
text-shadow: 2px 2px 4px #000; /* 添加字体阴影效果 */
|
||||
cursor: pointer;
|
||||
/* 确保边框和内边距不会影响宽高 */
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.expire-mask .refresh-text {
|
||||
color: #4183c4; /* 链接蓝色 */
|
||||
text-decoration: underline;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
|
||||
<script>
|
||||
|
||||
let timeoutQrTicketPolling = {{.wechatQrExpireSeconds}} * 1000 // 微信带参数临时二维码过期时间毫秒值
|
||||
let isQrTicketWaitingPolling = true
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
|
||||
// 提示微信二维码已经过期,停止轮询,并对二维码进行高斯模糊处理
|
||||
const idTimeoutWechatQrExpires = setTimeout(
|
||||
() => {
|
||||
if (isQrTicketWaitingPolling){
|
||||
/* 停止轮询 */
|
||||
isQrTicketWaitingPolling = false;
|
||||
|
||||
/* 创建遮罩层 */
|
||||
const qrContainer = document.querySelector('.wechat-qr-container');
|
||||
const expireMask = document.createElement('div');
|
||||
expireMask.className = 'expire-mask';
|
||||
expireMask.innerHTML = `
|
||||
<div>{{ctx.Locale.Tr "settings.wechat_qr_expired"}}</div><br/>
|
||||
{{svg "octicon-sync" 36}}
|
||||
`;
|
||||
expireMask.addEventListener('click', () => window.location.reload());
|
||||
qrContainer.appendChild(expireMask);
|
||||
}
|
||||
},
|
||||
timeoutQrTicketPolling
|
||||
);
|
||||
|
||||
// 定时查询微信二维码扫描状态
|
||||
const idIntervalWechatQrPolling = setInterval(() => {
|
||||
if (isQrTicketWaitingPolling) {
|
||||
checkWechatQrTicketStatus( "{{ .wechatQrTicket }}" );
|
||||
} else {
|
||||
// 当不再需要轮询时,清除定时器
|
||||
clearInterval(idIntervalWechatQrPolling);
|
||||
}
|
||||
},
|
||||
1000); // 每秒执行一次
|
||||
|
||||
});
|
||||
|
||||
|
||||
/*
|
||||
* 查询后台二维码扫描状态
|
||||
*
|
||||
* GET https://${window.location.host}/api/wechat/login/qr/check-status?ticket=${ticket}&_=${currentTimestamp}
|
||||
* 请求参数:
|
||||
* - ticket:微信公众号带参数二维码兑换凭证
|
||||
* - _ : 携带时间戳作为随机值,保证每次请求都能到达后端服务器,防止HTTP GET请求被浏览器缓存
|
||||
* 响应:
|
||||
* - 若用户未扫码,返回结果:{
|
||||
code: 10001,
|
||||
msg: "用户未扫码"
|
||||
}
|
||||
* - 若用户完成扫码,返回结果:{
|
||||
code: 0,
|
||||
msg:"扫码登录成功",
|
||||
data: {
|
||||
FromUserName: `${WechatQrScannerName}`
|
||||
}
|
||||
}
|
||||
*/
|
||||
function checkWechatQrTicketStatus(qrTicket) {
|
||||
const urlCheckWechatQrTicketStatus = `${window.location.origin}/api/wechat/login/qr/check-status?` +
|
||||
`ticket=${qrTicket}&` + `_=${Date.now()}`
|
||||
|
||||
if (!isQrTicketWaitingPolling)
|
||||
return
|
||||
|
||||
fetch(urlCheckWechatQrTicketStatus)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.code === 0 && data.data.is_scanned) { // 标识扫码成功
|
||||
console.log(data);
|
||||
onLoginSuccess(qrTicket, data.data);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('There was a problem with the fetch operation:', error);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function onLoginSuccess(qrTicket, qrStatus) {
|
||||
|
||||
// 1. 停止微信二维码轮询和过期判断
|
||||
isQrTicketWaitingPolling = false;
|
||||
|
||||
// 2. 移除二维码 img 标签
|
||||
const qrImageElement = document.getElementById('idWechatQr')
|
||||
if (qrImageElement) {
|
||||
qrImageElement.parentNode.removeChild(qrImageElement);
|
||||
}
|
||||
|
||||
// 3. 处理用户登录凭证 跳转到后端
|
||||
switch (window.location.pathname) {
|
||||
case '/user/login/wechat':
|
||||
// 登录页面扫码成功
|
||||
if (qrStatus.is_binded) {
|
||||
// 已绑定用户跳转登录成功页面 /user/login/wechat/success?ticket=${qrTicket}
|
||||
window.location.href = `${window.location.origin}/user/login/wechat/success?ticket=${qrTicket}&_=${Date.now()}`;
|
||||
} else {
|
||||
// 未绑定用户跳转到注册页 /user/sign_up?ticket=${qrTicket}
|
||||
alert(`微信用户 ${qrStatus.openid} 未注册!\n\n请点击确定继续注册账号,或者改用密码登录后进入用户设置页面扫码绑定微信`)
|
||||
window.location.href = `${window.location.origin}/user/sign_up?ticket=${qrTicket}&_=${Date.now()}`;
|
||||
}
|
||||
break;
|
||||
case '/user/settings/account':
|
||||
const confirmPrompt = '{{ctx.Locale.Tr "settings.wechat_bind_confirm" "${openid}" }}'.replace("${openid}", qrStatus.openid);
|
||||
if (window.confirm(confirmPrompt)) {
|
||||
// 绑定微信页面扫码成功:绑定成功页面 /user/settings/account/wechat/success?ticket=${qrTicket}
|
||||
window.location.href = `${window.location.origin}/user/settings/account/wechat/bind-success?ticket=${qrTicket}&_=${Date.now()}`
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.log(`尚未支持的扫码页面 ${window.location.pathname}`);
|
||||
alert(`微信用户 ${qrStatus.openid} 已成功扫码,但本页面不支持进一步操作,请联系管理员`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
{{/* ============================================================= 扫码失败,需要扫码 - 结束 ============================================================= */}}
|
||||
{{end}}
|
10
templates/user/auth/signup.tmpl
Normal file
10
templates/user/auth/signup.tmpl
Normal file
@@ -0,0 +1,10 @@
|
||||
{{template "base/head" .}}
|
||||
<div role="main" aria-label="{{.Title}}" class="page-content user signin{{if .LinkAccountMode}} icon{{end}}">
|
||||
{{template "user/auth/signin_navbar" .}}
|
||||
<div class="ui middle very relaxed page grid">
|
||||
<div class="column tw-flex tw-flex-col tw-gap-4 tw-max-w-2xl tw-m-auto">
|
||||
{{template "user/auth/signup_inner" .}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
80
templates/user/auth/signup_inner.tmpl
Normal file
80
templates/user/auth/signup_inner.tmpl
Normal file
@@ -0,0 +1,80 @@
|
||||
<div class="ui container fluid{{if .LinkAccountMode}} icon{{end}}">
|
||||
<h4 class="ui top attached header center">
|
||||
{{if .LinkAccountMode}}
|
||||
{{ctx.Locale.Tr "auth.oauth_signup_title"}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "sign_up"}}
|
||||
{{end}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
{{if .IsFirstTimeRegistration}}
|
||||
<p>{{ctx.Locale.Tr "auth.sign_up_tip"}}</p>
|
||||
{{end}}
|
||||
<form class="ui form" action="{{.SignUpLink}}" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
{{if or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister)}}
|
||||
{{template "base/alert" .}}
|
||||
{{end}}
|
||||
{{if .DisableRegistration}}
|
||||
<p>{{ctx.Locale.Tr "auth.disable_register_prompt"}}</p>
|
||||
{{else}}
|
||||
<div class="required field {{if and (.Err_UserName) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister))}}error{{end}}">
|
||||
<label for="user_name">{{ctx.Locale.Tr "username"}}</label>
|
||||
<input id="user_name" type="text" name="user_name" value="{{.user_name}}" autofocus required>
|
||||
</div>
|
||||
<div class="required field {{if .Err_Email}}error{{end}}">
|
||||
<label for="email">{{ctx.Locale.Tr "email"}}</label>
|
||||
<input id="email" name="email" type="email" value="{{.email}}" required>
|
||||
</div>
|
||||
|
||||
{{if not .DisablePassword}}
|
||||
<div class="required field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister))}}error{{end}}">
|
||||
<label for="password">{{ctx.Locale.Tr "password"}}</label>
|
||||
<input id="password" name="password" type="password" value="{{.password}}" autocomplete="new-password" required>
|
||||
</div>
|
||||
<div class="required field {{if and (.Err_Password) (or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeRegister))}}error{{end}}">
|
||||
<label for="retype">{{ctx.Locale.Tr "re_type"}}</label>
|
||||
<input id="retype" name="retype" type="password" value="{{.retype}}" autocomplete="new-password" required>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .EnableWechatQRSignIn}}
|
||||
<div class="required field {{if .Err_WechatQrTicket}}error{{end}}">
|
||||
<label for="register_bind_wechat">{{ctx.Locale.Tr "auth.register_bind_wechat"}}</label>
|
||||
<input id="id_wechat_qr_ticket" name="wechat_qr_ticket" type="text" value="{{.wechatQrTicket}}" placeholder={{ctx.Locale.Tr "auth.register_bind_wechat_helper_msg"}} required readonly>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{template "user/auth/captcha" .}}
|
||||
|
||||
<div class="inline field">
|
||||
<button class="ui primary button tw-w-full">
|
||||
{{if .LinkAccountMode}}
|
||||
{{ctx.Locale.Tr "auth.oauth_signup_submit"}}
|
||||
{{else}}
|
||||
{{ctx.Locale.Tr "auth.create_new_account"}}
|
||||
{{end}}
|
||||
</button>
|
||||
</div>
|
||||
{{end}}
|
||||
{{/* "oauth_container" contains not only "oauth2" methods, but also "OIDC" and "SSPI" methods */}}
|
||||
{{/* TODO: it seems that "EnableSSPI" is only set in "sign-in" handlers, but it should use the same logic to control its display */}}
|
||||
{{$showOAuth2Methods := or .OAuth2Providers .EnableOpenIDSignIn .EnableSSPI}}
|
||||
{{if $showOAuth2Methods}}
|
||||
<div class="divider divider-text">{{ctx.Locale.Tr "sign_in_or"}}</div>
|
||||
{{template "user/auth/oauth_container" .}}
|
||||
{{end}}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ui container fluid">
|
||||
{{if not .LinkAccountMode}}
|
||||
<div class="ui attached segment header top tw-flex tw-flex-col tw-items-center">
|
||||
<div class="field">
|
||||
<span>{{ctx.Locale.Tr "auth.already_have_account"}}</span>
|
||||
<a href="{{AppSubUrl}}/user/login">{{ctx.Locale.Tr "auth.sign_in_now"}}</a>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
36
templates/user/auth/signup_openid_connect.tmpl
Normal file
36
templates/user/auth/signup_openid_connect.tmpl
Normal file
@@ -0,0 +1,36 @@
|
||||
{{template "base/head" .}}
|
||||
<div role="main" aria-label="{{.Title}}" class="page-content user signup">
|
||||
{{template "user/auth/signup_openid_navbar" .}}
|
||||
<div class="ui container medium-width">
|
||||
{{template "base/alert" .}}
|
||||
<h4 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "auth.openid_connect_title"}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
<form class="ui form left-right-form" action="{{.Link}}" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="inline field">
|
||||
<span class="help">{{ctx.Locale.Tr "auth.openid_connect_desc"}}</span>
|
||||
</div>
|
||||
<div class="required inline field {{if .Err_UserName}}error{{end}}">
|
||||
<label for="user_name">{{ctx.Locale.Tr "home.uname_holder"}}</label>
|
||||
<input id="user_name" type="text" name="user_name" value="{{.user_name}}" autofocus required>
|
||||
</div>
|
||||
<div class="required inline field {{if .Err_Password}}error{{end}}">
|
||||
<label for="password">{{ctx.Locale.Tr "password"}}</label>
|
||||
<input id="password" name="password" type="password" value="{{.password}}" autocomplete="off" required>
|
||||
</div>
|
||||
<div class="inline field">
|
||||
<label for="openid">OpenID URI</label>
|
||||
<input id="openid" value="{{.OpenID}}" readonly>
|
||||
</div>
|
||||
<div class="inline field">
|
||||
<label></label>
|
||||
<button class="ui primary button">{{ctx.Locale.Tr "auth.openid_connect_submit"}}</button>
|
||||
<a href="{{AppSubUrl}}/user/forgot_password">{{ctx.Locale.Tr "auth.forgot_password"}}</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
12
templates/user/auth/signup_openid_navbar.tmpl
Normal file
12
templates/user/auth/signup_openid_navbar.tmpl
Normal file
@@ -0,0 +1,12 @@
|
||||
<overflow-menu class="ui secondary pointing tabular top attached borderless menu secondary-nav">
|
||||
<div class="overflow-menu-items tw-justify-center">
|
||||
<a class="{{if .PageIsOpenIDConnect}}active {{end}}item" href="{{AppSubUrl}}/user/openid/connect">
|
||||
{{ctx.Locale.Tr "auth.openid_connect_title"}}
|
||||
</a>
|
||||
{{if and .EnableOpenIDSignUp (not .AllowOnlyInternalRegistration)}}
|
||||
<a class="{{if .PageIsOpenIDRegister}}active {{end}}item" href="{{AppSubUrl}}/user/openid/register">
|
||||
{{ctx.Locale.Tr "auth.openid_register_title"}}
|
||||
</a>
|
||||
{{end}}
|
||||
</div>
|
||||
</overflow-menu>
|
37
templates/user/auth/signup_openid_register.tmpl
Normal file
37
templates/user/auth/signup_openid_register.tmpl
Normal file
@@ -0,0 +1,37 @@
|
||||
{{template "base/head" .}}
|
||||
<div role="main" aria-label="{{.Title}}" class="page-content user signup">
|
||||
{{template "user/auth/signup_openid_navbar" .}}
|
||||
<div class="ui container">
|
||||
{{template "base/alert" .}}
|
||||
<h4 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "auth.openid_register_title"}}
|
||||
</h4>
|
||||
<div class="ui attached segment">
|
||||
<p>
|
||||
{{ctx.Locale.Tr "auth.openid_register_desc"}}
|
||||
</p>
|
||||
<form class="ui form" action="{{.Link}}" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<div class="required field {{if .Err_UserName}}error{{end}}">
|
||||
<label for="user_name">{{ctx.Locale.Tr "username"}}</label>
|
||||
<input id="user_name" type="text" name="user_name" value="{{.user_name}}" autofocus required>
|
||||
</div>
|
||||
<div class="required field {{if .Err_Email}}error{{end}}">
|
||||
<label for="email">{{ctx.Locale.Tr "email"}}</label>
|
||||
<input id="email" name="email" type="email" value="{{.email}}" required>
|
||||
</div>
|
||||
|
||||
{{template "user/auth/captcha" .}}
|
||||
|
||||
<div class="field">
|
||||
<label for="openid">OpenID URI</label>
|
||||
<input id="openid" value="{{.OpenID}}" readonly>
|
||||
</div>
|
||||
<div class="inline field">
|
||||
<button class="ui primary button">{{ctx.Locale.Tr "auth.create_new_account"}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
26
templates/user/auth/twofa.tmpl
Normal file
26
templates/user/auth/twofa.tmpl
Normal file
@@ -0,0 +1,26 @@
|
||||
{{template "base/head" .}}
|
||||
<div role="main" aria-label="{{.Title}}" class="page-content user signin">
|
||||
<div class="ui middle very relaxed page grid">
|
||||
<div class="column">
|
||||
<form class="ui form tw-max-w-2xl tw-m-auto" action="{{.Link}}" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<h3 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "twofa"}}
|
||||
</h3>
|
||||
<div class="ui attached segment">
|
||||
{{template "base/alert" .}}
|
||||
<div class="required field">
|
||||
<label for="passcode">{{ctx.Locale.Tr "passcode"}}</label>
|
||||
<input id="passcode" name="passcode" type="text" autocomplete="one-time-code" inputmode="numeric" pattern="[0-9]*" autofocus required>
|
||||
</div>
|
||||
|
||||
<div class="inline field">
|
||||
<button class="ui primary button">{{ctx.Locale.Tr "auth.verify"}}</button>
|
||||
<a href="{{AppSubUrl}}/user/two_factor/scratch">{{ctx.Locale.Tr "auth.use_scratch_code"}}</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
25
templates/user/auth/twofa_scratch.tmpl
Normal file
25
templates/user/auth/twofa_scratch.tmpl
Normal file
@@ -0,0 +1,25 @@
|
||||
{{template "base/head" .}}
|
||||
<div role="main" aria-label="{{.Title}}" class="page-content user signin">
|
||||
<div class="ui middle very relaxed page grid">
|
||||
<div class="column">
|
||||
<form class="ui form tw-max-w-2xl tw-m-auto" action="{{.Link}}" method="post">
|
||||
{{.CsrfTokenHtml}}
|
||||
<h3 class="ui top attached header">
|
||||
{{ctx.Locale.Tr "twofa_scratch"}}
|
||||
</h3>
|
||||
<div class="ui attached segment">
|
||||
{{template "base/alert" .}}
|
||||
<div class="required field">
|
||||
<label for="token">{{ctx.Locale.Tr "auth.scratch_code"}}</label>
|
||||
<input id="token" name="token" type="text" autocomplete="off" autofocus required>
|
||||
</div>
|
||||
|
||||
<div class="inline field">
|
||||
<button class="ui primary button">{{ctx.Locale.Tr "auth.verify"}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
25
templates/user/auth/webauthn.tmpl
Normal file
25
templates/user/auth/webauthn.tmpl
Normal file
@@ -0,0 +1,25 @@
|
||||
{{template "base/head" .}}
|
||||
<div role="main" aria-label="{{.Title}}" class="page-content user signin webauthn-prompt">
|
||||
<div class="ui page grid">
|
||||
<div class="column tw-text-center">
|
||||
{{template "user/auth/webauthn_error" .}}
|
||||
<h3 class="ui top attached header">{{ctx.Locale.Tr "twofa"}}</h3>
|
||||
<div class="ui attached segment">
|
||||
{{svg "octicon-key" 56}}
|
||||
<h3>{{ctx.Locale.Tr "webauthn_insert_key"}}</h3>
|
||||
{{template "base/alert" .}}
|
||||
<p>{{ctx.Locale.Tr "webauthn_sign_in"}}</p>
|
||||
</div>
|
||||
<div class="ui attached segment tw-flex tw-items-center tw-justify-center tw-gap-1 tw-py-2">
|
||||
<div class="is-loading tw-w-[40px] tw-h-[40px]"></div>
|
||||
{{ctx.Locale.Tr "webauthn_press_button"}}
|
||||
</div>
|
||||
{{if .HasTwoFactor}}
|
||||
<div class="ui attached segment">
|
||||
<a href="{{AppSubUrl}}/user/two_factor">{{ctx.Locale.Tr "webauthn_use_twofa"}}</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{template "base/footer" .}}
|
13
templates/user/auth/webauthn_error.tmpl
Normal file
13
templates/user/auth/webauthn_error.tmpl
Normal file
@@ -0,0 +1,13 @@
|
||||
<div id="webauthn-error" class="ui negative message tw-hidden">
|
||||
<div class="header">{{ctx.Locale.Tr "webauthn_error"}}</div>
|
||||
<div id="webauthn-error-msg" class="tw-pt-2"></div>
|
||||
<div class="tw-hidden">
|
||||
<div data-webauthn-error-msg="browser">{{ctx.Locale.Tr "webauthn_unsupported_browser"}}</div>
|
||||
<div data-webauthn-error-msg="unknown">{{ctx.Locale.Tr "webauthn_error_unknown"}}</div>
|
||||
<div data-webauthn-error-msg="insecure">{{ctx.Locale.Tr "webauthn_error_insecure"}}</div>
|
||||
<div data-webauthn-error-msg="unable-to-process">{{ctx.Locale.Tr "webauthn_error_unable_to_process"}}</div>
|
||||
<div data-webauthn-error-msg="duplicated">{{ctx.Locale.Tr "webauthn_error_duplicated"}}</div>
|
||||
<div data-webauthn-error-msg="empty">{{ctx.Locale.Tr "webauthn_error_empty"}}</div>
|
||||
<div data-webauthn-error-msg="timeout">{{ctx.Locale.Tr "webauthn_error_timeout"}}</div>
|
||||
</div>
|
||||
</div>
|
Reference in New Issue
Block a user