Skip to content
This repository has been archived by the owner on Jan 23, 2022. It is now read-only.

Commit

Permalink
add mfa app testing facilities
Browse files Browse the repository at this point in the history
Partial Resolution: #13
  • Loading branch information
greenpau committed Dec 7, 2020
1 parent 8b7ffa0 commit d2e0291
Show file tree
Hide file tree
Showing 5 changed files with 193 additions and 7 deletions.
57 changes: 55 additions & 2 deletions assets/templates/basic/settings.template
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,12 @@
</div>
<div class="card-action">
<a href="{{ pathjoin $.ActionEndpoint "/settings/mfa/delete/" .ID }}">Delete</a>
<a href="{{ pathjoin $.ActionEndpoint "/settings/mfa/test/" .ID }}">Test</a>
{{ if eq .Type "totp" }}
<a href="{{ pathjoin $.ActionEndpoint "/settings/mfa/test/app/" .ID }}">Test</a>
{{ end }}
{{ if eq .Type "u2f" }}
<a href="{{ pathjoin $.ActionEndpoint "/settings/mfa/test/u2f/" .ID }}">Test</a>
{{ end }}
</div>
</div>
{{ end }}
Expand Down Expand Up @@ -337,7 +342,7 @@
<input id="secret" name="secret" type="hidden" value="{{ .Data.mfa_secret }}" />
<input id="type" name="type" type="hidden" value="{{ .Data.mfa_type }}" />
<input id="period" name="period" type="hidden" value="{{ .Data.mfa_period }}" />
<input id="period" name="digits" type="hidden" value="{{ .Data.mfa_digits }}" />
<input id="digits" name="digits" type="hidden" value="{{ .Data.mfa_digits }}" />
</div>
<div class="col s12 m6 l6">
<div class="center-align"><img src="{{ pathjoin .ActionEndpoint "/settings/mfa/barcode/" .Data.code_uri_encoded }}.png" alt="QR Code" /></div>
Expand Down Expand Up @@ -376,6 +381,54 @@
</div>
</div>
{{ end }}
{{ if eq .Data.view "mfa-test-app" }}
<form action="{{ pathjoin .ActionEndpoint "/settings/mfa/test/app/" .Data.mfa_token_id }}" method="POST">
<div class="row">
<h1>Test MFA Authenticator Application</h1>
<div class="row">
<div class="col s12 m6 l6">
<p>Please open your MFA authenticator application to view your authentication code and verify your identity</p>
<div class="input-field">
<input id="passcode" name="passcode" type="text" class="validate" pattern="[0-9]{6}"
title="Passcode should contain 6 characters and consists of 0-9 characters."
required />
<label for="passcode">Passcode</label>
</div>
<input id="token_id" name="token_id" type="hidden" value="{{ .Data.mfa_token_id }}" />
</div>
</div>
</div>
<div class="row">
<button type="submit" name="submit" class="btn waves-effect waves-light navbtn active navbtn-last app-btn">
<i class="las la-plus-circle left app-btn-icon"></i>
<span class="app-btn-text">Validate</span>
</button>
</div>
</form>
{{ end }}
{{ if eq .Data.view "mfa-test-app-status" }}
<div class="row">
<div class="col s12">
<h1>Test MFA Authenticator Application</h1>
<p>{{.Data.status }}: {{ .Data.status_reason }}</p>
{{ if eq .Data.status "SUCCESS" }}
<a href="{{ pathjoin .ActionEndpoint "/settings/mfa" }}">
<button type="button" class="btn waves-effect waves-light navbtn active">
<i class="las la-undo-alt left app-btn-icon"></i>
<span class="app-btn-text">Go Back</span>
</button>
</a>
{{ else }}
<a href="{{ pathjoin .ActionEndpoint "/settings/mfa/test/app/" .Data.mfa_token_id }}">
<button type="button" class="btn waves-effect waves-light navbtn active">
<i class="las la-undo-alt left app-btn-icon"></i>
<span class="app-btn-text">Try Again</span>
</button>
</a>
{{ end }}
</div>
</div>
{{ end }}
{{ if eq .Data.view "mfa-delete-status" }}
<div class="row">
<div class="col s12">
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require (
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/go-ldap/ldap v3.0.3+incompatible
github.com/greenpau/caddy-auth-jwt v1.2.1
github.com/greenpau/go-identity v1.0.17
github.com/greenpau/go-identity v1.0.18
github.com/satori/go.uuid v1.2.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
go.uber.org/zap v1.15.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -301,8 +301,8 @@ github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.m
github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE=
github.com/greenpau/caddy-auth-jwt v1.2.1 h1:m04X6g6gvbMIdTpPbPYZRU4RQaFy5cKQk7ym/j6m/Tg=
github.com/greenpau/caddy-auth-jwt v1.2.1/go.mod h1:XS1wGcSSSS9H544mis5b640ae/hNL58Nbu/tCU6PVZo=
github.com/greenpau/go-identity v1.0.17 h1:aGs7T5tI47p9nBFn/gRyT0AMVUYnG2YsTMiu0hHNN5U=
github.com/greenpau/go-identity v1.0.17/go.mod h1:++pwfuoXgXJQM1PFtiHTH9fMlbgOY42Hs5wSwoQ3JK0=
github.com/greenpau/go-identity v1.0.18 h1:F0V3gSqPYWGiGIE3121tY05JOrYxYym8aapIrBcIYjY=
github.com/greenpau/go-identity v1.0.18/go.mod h1:++pwfuoXgXJQM1PFtiHTH9fMlbgOY42Hs5wSwoQ3JK0=
github.com/greenpau/versioned v1.0.23 h1:ICqCoTG8Xv92BV+bKs52d86pDF/e0zhk3LLELsYMpl4=
github.com/greenpau/versioned v1.0.23/go.mod h1:rtFCvaWWNbMH4CJnje/xicgmrM63j++rUh5juSu0k/A=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
Expand Down
80 changes: 80 additions & 0 deletions pkg/handlers/settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,59 @@ func ServeSettings(w http.ResponseWriter, r *http.Request, opts map[string]inter
}
}
}
case "test":
if len(viewParts) > 3 {
resp.Data["mfa_token_id"] = strings.TrimSpace(viewParts[3])
if r.Method == "POST" {
resp.Data["status"] = "FAIL"
if backend != nil {
switch viewParts[2] {
case "app":
view = "mfa-test-app-status"
var passcodeValid bool
if tokenID, passcode, err := validateMfaAuthTokenForm(r); err != nil {
resp.Data["status_reason"] = fmt.Sprintf("Bad Request: %s", err)
} else {
args := make(map[string]interface{})
args["username"] = claims.Subject
args["email"] = claims.Email
mfaTokens, err := backend.GetMfaTokens(args)
if err != nil {
resp.Data["status_reason"] = fmt.Sprintf("%s", err)
} else {
for _, mfaToken := range mfaTokens {
if mfaToken.ID != tokenID {
continue
}
if err := mfaToken.ValidateCode(passcode); err == nil {
passcodeValid = true
resp.Data["status"] = "SUCCESS"
resp.Data["status_reason"] = fmt.Sprintf("token %s validated successfully", mfaToken.ID)
break
}
}
if !passcodeValid {
resp.Data["status_reason"] = fmt.Sprintf("invalid passcode")
}
}
}
case "u2f":
view = "mfa-test-u2f-status"
resp.Data["status_reason"] = "Not implemented"
}
} else {
resp.Data["status_reason"] = "Authentication backend not found"
}
} else {
// Get token ID from path
switch viewParts[2] {
case "app":
view = "mfa-test-app"
case "u2f":
view = "mfa-test-u2f"
}
}
}
}
} else {
// Entry Page
Expand Down Expand Up @@ -406,6 +459,33 @@ func validateKeyInputForm(r *http.Request) (map[string]string, error) {
return resp, nil
}

func validateMfaAuthTokenForm(r *http.Request) (string, string, error) {
if r.Header.Get("Content-Type") != "application/x-www-form-urlencoded" {
return "", "", fmt.Errorf("Unsupported content type")
}
if err := r.ParseForm(); err != nil {
return "", "", fmt.Errorf("Failed parsing submitted form")
}

passcode := r.PostFormValue("passcode")
passcode = strings.TrimSpace(passcode)
if passcode == "" {
return "", "", fmt.Errorf("Required form passcode field is empty")
}

if len(passcode) < 4 || len(passcode) > 8 {
return "", "", fmt.Errorf("MFA passcode is not 4-8 characters long")
}

tokenID := r.PostFormValue("token_id")
tokenID = strings.TrimSpace(tokenID)
if tokenID == "" {
return "", "", fmt.Errorf("Required form token_id field is empty")
}

return tokenID, passcode, nil
}

func validateAddMfaTokenForm(r *http.Request) (map[string]string, error) {
resp := make(map[string]string)
if r.Header.Get("Content-Type") != "application/x-www-form-urlencoded" {
Expand Down
57 changes: 55 additions & 2 deletions pkg/ui/pages.go
Original file line number Diff line number Diff line change
Expand Up @@ -792,7 +792,12 @@ var PageTemplates = map[string]string{
</div>
<div class="card-action">
<a href="{{ pathjoin $.ActionEndpoint "/settings/mfa/delete/" .ID }}">Delete</a>
<a href="{{ pathjoin $.ActionEndpoint "/settings/mfa/test/" .ID }}">Test</a>
{{ if eq .Type "totp" }}
<a href="{{ pathjoin $.ActionEndpoint "/settings/mfa/test/app/" .ID }}">Test</a>
{{ end }}
{{ if eq .Type "u2f" }}
<a href="{{ pathjoin $.ActionEndpoint "/settings/mfa/test/u2f/" .ID }}">Test</a>
{{ end }}
</div>
</div>
{{ end }}
Expand Down Expand Up @@ -832,7 +837,7 @@ var PageTemplates = map[string]string{
<input id="secret" name="secret" type="hidden" value="{{ .Data.mfa_secret }}" />
<input id="type" name="type" type="hidden" value="{{ .Data.mfa_type }}" />
<input id="period" name="period" type="hidden" value="{{ .Data.mfa_period }}" />
<input id="period" name="digits" type="hidden" value="{{ .Data.mfa_digits }}" />
<input id="digits" name="digits" type="hidden" value="{{ .Data.mfa_digits }}" />
</div>
<div class="col s12 m6 l6">
<div class="center-align"><img src="{{ pathjoin .ActionEndpoint "/settings/mfa/barcode/" .Data.code_uri_encoded }}.png" alt="QR Code" /></div>
Expand Down Expand Up @@ -871,6 +876,54 @@ var PageTemplates = map[string]string{
</div>
</div>
{{ end }}
{{ if eq .Data.view "mfa-test-app" }}
<form action="{{ pathjoin .ActionEndpoint "/settings/mfa/test/app/" .Data.mfa_token_id }}" method="POST">
<div class="row">
<h1>Test MFA Authenticator Application</h1>
<div class="row">
<div class="col s12 m6 l6">
<p>Please open your MFA authenticator application to view your authentication code and verify your identity</p>
<div class="input-field">
<input id="passcode" name="passcode" type="text" class="validate" pattern="[0-9]{6}"
title="Passcode should contain 6 characters and consists of 0-9 characters."
required />
<label for="passcode">Passcode</label>
</div>
<input id="token_id" name="token_id" type="hidden" value="{{ .Data.mfa_token_id }}" />
</div>
</div>
</div>
<div class="row">
<button type="submit" name="submit" class="btn waves-effect waves-light navbtn active navbtn-last app-btn">
<i class="las la-plus-circle left app-btn-icon"></i>
<span class="app-btn-text">Validate</span>
</button>
</div>
</form>
{{ end }}
{{ if eq .Data.view "mfa-test-app-status" }}
<div class="row">
<div class="col s12">
<h1>Test MFA Authenticator Application</h1>
<p>{{.Data.status }}: {{ .Data.status_reason }}</p>
{{ if eq .Data.status "SUCCESS" }}
<a href="{{ pathjoin .ActionEndpoint "/settings/mfa" }}">
<button type="button" class="btn waves-effect waves-light navbtn active">
<i class="las la-undo-alt left app-btn-icon"></i>
<span class="app-btn-text">Go Back</span>
</button>
</a>
{{ else }}
<a href="{{ pathjoin .ActionEndpoint "/settings/mfa/test/app/" .Data.mfa_token_id }}">
<button type="button" class="btn waves-effect waves-light navbtn active">
<i class="las la-undo-alt left app-btn-icon"></i>
<span class="app-btn-text">Try Again</span>
</button>
</a>
{{ end }}
</div>
</div>
{{ end }}
{{ if eq .Data.view "mfa-delete-status" }}
<div class="row">
<div class="col s12">
Expand Down

0 comments on commit d2e0291

Please sign in to comment.