feat(login): add OTP (email and sms) (#6353)

* feat: login with otp

* fix(i18n): japanese translation

* add missing files

* fix provider change

* add event types translations to en

* add tests

* resourceOwner

* remove unused handler

* fix: secret generators and add comments

* add setup step

* rename

* linting

* fix setup

* improve otp handling

* fix autocomplete

* translations for login and notifications

* translations for event types

* changes from review

* check selected mfa type
This commit is contained in:
Livio Spring
2023-08-15 12:47:05 +00:00
committed by GitHub
parent faa9ed4de9
commit 7c494fd219
76 changed files with 3203 additions and 88 deletions
@@ -0,0 +1,63 @@
{{template "main-top" .}}
<div class="lgn-head">
<h1>{{t "InitMFAOTPSMS.Title"}}</h1>
{{ template "user-profile" . }}
{{if .Edit}}
<p>{{t "InitMFAOTPSMS.DescriptionPhone"}}</p>
{{else}}
<p>{{t "InitMFAOTPSMS.DescriptionCode"}}</p>
{{end}}
</div>
<form action="{{ mfaInitSMSVerifyUrl }}" method="POST">
{{ .CSRF }}
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
<input type="hidden" name="mfaType" value="{{ .MFAType }}" />
<input type="hidden" name="phone" value="{{ .Phone }}" />
<div class="fields">
{{if .Edit}}
<div class="field">
<label class="lgn-label" for="newPhone">{{t "InitMFAOTPSMS.PhoneLabel"}}</label>
<input class="lgn-input" type="tel" id="newPhone" name="newPhone" autocomplete="off" value="{{.Phone}}" autofocus required>
</div>
{{else}}
<div class="field lgn-actions">
<p>{{.Phone}}</p>
<span class="fill-space"></span>
<button type="button" id="edit" name="edit" value="true" class="lgn-stroked-button" formnovalidate>{{t "InitMFAOTPSMS.EditButtonText"}}</button>
</div>
{{end}}
{{if and .Phone (not .Edit) }}
<div class="field">
<label class="lgn-label" for="code">{{t "InitMFAOTPSMS.CodeLabel"}}</label>
<input class="lgn-input" type="text" id="code" name="code" autocomplete="one-time-code" autofocus required>
</div>
{{end}}
</div>
{{ template "error-message" .}}
<div class="lgn-actions lgn-reverse-order">
<!-- position element in header -->
<a class="lgn-icon-button lgn-left-action" href="{{ mfaPromptChangeUrl .AuthReqID .MFAType }}">
<i class="lgn-icon-arrow-left-solid"></i>
</a>
<button class="lgn-raised-button lgn-primary" id="submit-button" type="submit">{{t "InitMFAOTPSMS.NextButtonText"}}</button>
{{if and .Phone (not .Edit) }}
<span class="fill-space"></span>
<button type="submit" name="resend" value="true" class="lgn-stroked-button" formnovalidate>{{t "InitMFAOTPSMS.ResendButtonText"}}</button>
{{end}}
</div>
</form>
<script src="{{ resourceUrl "scripts/edit.js" }}"></script>
<script src="{{ resourceUrl "scripts/form_submit.js" }}"></script>
<script src="{{ resourceUrl "scripts/default_form_validation.js" }}"></script>
{{template "main-bottom" .}}
@@ -34,6 +34,16 @@
<img width="100px" height="100px" alt="OTP" src="{{ resourceUrl
"images/mfa/mfa-u2f.svg" }}" />
</div>
{{ end }} {{ if eq $provider 3 }}
<div class="mfa-img">
<img width="100px" height="100px" alt="OTP SMS" src="{{ resourceUrl
"images/mfa/mfa-u2f.svg" }}" /> // TODO: image
</div>
{{ end }}{{ if eq $provider 4 }}
<div class="mfa-img">
<img width="100px" height="100px" alt="OTP Email" src="{{ resourceUrl
"images/mfa/mfa-u2f.svg" }}" /> // TODO: image
</div>
{{ end }}
<span>{{ $providerName }} </span>
</label>
@@ -1,34 +1,37 @@
{{template "main-top" .}}
<div class="lgn-head">
<h1>{{t "VerifyMFAOTP.Title"}}</h1>
<h1>{{t "VerifyOTP.Title"}}</h1>
{{ template "user-profile" . }}
<p>{{t "VerifyMFAOTP.Description"}}</p>
<p>{{t "VerifyOTP.Description"}}</p>
</div>
<form action="{{ mfaVerifyUrl }}" method="POST">
<form action="{{ mfaOTPVerifyUrl }}" method="POST">
{{ .CSRF }}
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
<input type="hidden" name="mfaType" value="{{ .SelectedMFAProvider }}" />
<input type="hidden" name="selectedProvider" value="{{ .SelectedProvider }}" />
<div class="fields">
<label class="lgn-label" for="code">{{t "VerifyMFAOTP.CodeLabel"}}</label>
<input class="lgn-input" type="text" id="code" name="code" autocomplete="off" autofocus required>
<label class="lgn-label" for="code">{{t "VerifyOTP.CodeLabel"}}</label>
<input class="lgn-input" type="text" id="code" name="code" autocomplete="one-time-code" autofocus required>
</div>
{{ template "error-message" .}}
<div class="lgn-actions">
<div class="lgn-actions lgn-reverse-order">
<!-- position element in header -->
<a class="lgn-icon-button lgn-left-action" href="{{ loginUrl }}">
<i class="lgn-icon-arrow-left-solid"></i>
</a>
<button class="lgn-raised-button lgn-primary" id="submit-button" type="submit">{{t "VerifyOTP.NextButtonText"}}</button>
<span class="fill-space"></span>
<button class="lgn-raised-button lgn-primary" id="submit-button" type="submit">{{t "VerifyMFAOTP.NextButtonText"}}</button>
<button type="submit" name="resend" value="true" class="lgn-stroked-button" formnovalidate>{{t "VerifyOTP.ResendButtonText"}}</button>
</div>
{{ if .MFAProviders }}
@@ -45,4 +48,4 @@
<script src="{{ resourceUrl "scripts/form_submit.js" }}"></script>
<script src="{{ resourceUrl "scripts/default_form_validation.js" }}"></script>
{{template "main-bottom" .}}
{{template "main-bottom" .}}
@@ -0,0 +1,48 @@
{{template "main-top" .}}
<div class="lgn-head">
<h1>{{t "VerifyMFAOTP.Title"}}</h1>
{{ template "user-profile" . }}
<p>{{t "VerifyMFAOTP.Description"}}</p>
</div>
<form action="{{ mfaVerifyUrl }}" method="POST">
{{ .CSRF }}
<input type="hidden" name="authRequestID" value="{{ .AuthReqID }}" />
<input type="hidden" name="mfaType" value="{{ .SelectedMFAProvider }}" />
<div class="fields">
<label class="lgn-label" for="code">{{t "VerifyMFAOTP.CodeLabel"}}</label>
<input class="lgn-input" type="text" id="code" name="code" autocomplete="off" autofocus required>
</div>
{{ template "error-message" .}}
<div class="lgn-actions">
<!-- position element in header -->
<a class="lgn-icon-button lgn-left-action" href="{{ loginUrl }}">
<i class="lgn-icon-arrow-left-solid"></i>
</a>
<span class="fill-space"></span>
<button class="lgn-raised-button lgn-primary" id="submit-button" type="submit">{{t "VerifyMFAOTP.NextButtonText"}}</button>
</div>
{{ if .MFAProviders }}
<div class="lgn-mfa-other">
<p>{{t "MFAProvider.ChooseOther"}}</p>
{{ range $provider := .MFAProviders}}
{{ $providerName := (t (printf "MFAProvider.Provider%v" $provider)) }}
<button class="lgn-stroked-button" type="submit" name="provider" value="{{$provider}}"
formnovalidate>{{$providerName}}</button>
{{ end }}
</div>
{{ end }}
</form>
<script src="{{ resourceUrl "scripts/form_submit.js" }}"></script>
<script src="{{ resourceUrl "scripts/default_form_validation.js" }}"></script>
{{template "main-bottom" .}}