summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Crute <mike@crute.us>2023-07-31 13:34:44 -0700
committerMike Crute <mike@crute.us>2023-07-31 13:34:44 -0700
commitdb217bbb1f74b7aa955d3095fef62c71946768cf (patch)
tree80ee49b11f137412cf440262950e4a116099eb24
parent6d867608837f879be2eb934d034f49359f973c84 (diff)
downloadwebsocket_proxy-db217bbb1f74b7aa955d3095fef62c71946768cf.tar.bz2
websocket_proxy-db217bbb1f74b7aa955d3095fef62c71946768cf.tar.xz
websocket_proxy-db217bbb1f74b7aa955d3095fef62c71946768cf.zip
Complete registration flow
-rw-r--r--.gitignore1
-rw-r--r--Makefile3
-rw-r--r--app/controllers/register.go75
-rw-r--r--cmd/web/server.go10
-rw-r--r--main.go5
-rw-r--r--templates/js/base64.js66
-rw-r--r--templates/login.tpl92
-rw-r--r--templates/register.tpl196
8 files changed, 238 insertions, 210 deletions
diff --git a/.gitignore b/.gitignore
index 048c44e..039bf37 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
1/clients/
1/ssh-proxy 2/ssh-proxy
2/ssh-proxy-client 3/ssh-proxy-client
3/ssl/ 4/ssl/
diff --git a/Makefile b/Makefile
index 81601bc..cc3fc5f 100644
--- a/Makefile
+++ b/Makefile
@@ -16,7 +16,8 @@ $(CLIENT_BINARY): $(shell find . -name '*.go')
16 CGO_ENABLED=0 go build \ 16 CGO_ENABLED=0 go build \
17 -tags clientonly \ 17 -tags clientonly \
18 -ldflags "-X main.appVersion=$(shell git describe --long --tags --dirty --always) \ 18 -ldflags "-X main.appVersion=$(shell git describe --long --tags --dirty --always) \
19 -X code.crute.us/mcrute/ssh-proxy/cmd/client.clientId=$(CLIENT_ID)" \ 19 -X code.crute.us/mcrute/ssh-proxy/cmd/client.clientId=$(CLIENT_ID) \
20 -s -w" \
20 -o $@ 21 -o $@
21 22
22.PHONY: vet 23.PHONY: vet
diff --git a/app/controllers/register.go b/app/controllers/register.go
index 8698bda..7c1a0f3 100644
--- a/app/controllers/register.go
+++ b/app/controllers/register.go
@@ -1,7 +1,13 @@
1package controllers 1package controllers
2 2
3import ( 3import (
4 "bytes"
5 "context"
6 "encoding/json"
7 "fmt"
8 "io"
4 "net/http" 9 "net/http"
10 "time"
5 11
6 "code.crute.us/mcrute/golib/echo/session" 12 "code.crute.us/mcrute/golib/echo/session"
7 "code.crute.us/mcrute/ssh-proxy/app" 13 "code.crute.us/mcrute/ssh-proxy/app"
@@ -19,13 +25,45 @@ type RegisterController[T app.AppSession] struct {
19 Webauthn *webauthn.WebAuthn 25 Webauthn *webauthn.WebAuthn
20} 26}
21 27
28func (a *RegisterController[T]) validateRequest(ctx context.Context, u *models.User, code string) (*models.AuthSession, error) {
29 if code == "" {
30 return nil, fmt.Errorf("Code not passed in request")
31 }
32
33 authSession, err := a.AuthSessions.GetByUserCode(ctx, code)
34 if err != nil {
35 return nil, fmt.Errorf("No auth session exists")
36 }
37
38 if time.Now().After(authSession.Expires) {
39 return nil, fmt.Errorf("Session is expired")
40 }
41
42 if !authSession.IsRegistration {
43 return nil, fmt.Errorf("Session is not an invitation to register")
44 }
45
46 if authSession.UserId != u.Username {
47 return nil, fmt.Errorf("Session not valid for this user")
48 }
49
50 return authSession, nil
51}
52
22func (a *RegisterController[T]) HandleStart(c echo.Context) error { 53func (a *RegisterController[T]) HandleStart(c echo.Context) error {
23 user, err := a.Users.Get(c.Request().Context(), c.Param("username")) 54 ctx := c.Request().Context()
55
56 user, err := a.Users.Get(ctx, c.Param("username"))
24 if err != nil { 57 if err != nil {
25 a.Logger.Errorf("Error getting user: %s", err) 58 a.Logger.Errorf("Error getting user: %s", err)
26 return c.NoContent(http.StatusNotFound) 59 return c.NoContent(http.StatusNotFound)
27 } 60 }
28 61
62 if _, err := a.validateRequest(ctx, user, c.QueryParam("code")); err != nil {
63 a.Logger.Errorf("Error creating registration request: %s", err)
64 return c.NoContent(http.StatusNotFound)
65 }
66
29 request, sessionData, err := a.Webauthn.BeginRegistration(user) 67 request, sessionData, err := a.Webauthn.BeginRegistration(user)
30 if err != nil { 68 if err != nil {
31 a.Logger.Errorf("Error creating webauthn request: %s", err) 69 a.Logger.Errorf("Error creating webauthn request: %s", err)
@@ -41,13 +79,42 @@ func (a *RegisterController[T]) HandleStart(c echo.Context) error {
41} 79}
42 80
43func (a *RegisterController[T]) HandleFinish(c echo.Context) error { 81func (a *RegisterController[T]) HandleFinish(c echo.Context) error {
44 user, err := a.Users.Get(c.Request().Context(), c.Param("username")) 82 ctx := c.Request().Context()
83
84 body, err := io.ReadAll(c.Request().Body)
85 if err != nil {
86 a.Logger.Errorf("Error reading request body:", err)
87 return c.NoContent(http.StatusInternalServerError)
88 }
89
90 user, err := a.Users.Get(ctx, c.Param("username"))
45 if err != nil { 91 if err != nil {
46 a.Logger.Errorf("Error getting user: %s", err) 92 a.Logger.Errorf("Error getting user: %s", err)
47 return c.NoContent(http.StatusNotFound) 93 return c.NoContent(http.StatusNotFound)
48 } 94 }
49 95
50 response, err := protocol.ParseCredentialCreationResponseBody(c.Request().Body) 96 var code struct {
97 Code string `json:"code"`
98 }
99 if err := json.Unmarshal(body, &code); err != nil {
100 a.Logger.Errorf("Error decoding json body")
101 return c.NoContent(http.StatusBadRequest)
102 }
103
104 authSession, err := a.validateRequest(ctx, user, code.Code)
105 if err != nil {
106 a.Logger.Errorf("Error finishing register request: %s", err)
107 return c.NoContent(http.StatusNotFound)
108 }
109
110 // Delete before anything else to avoid allowing double use of an auth
111 // session in case of other errors
112 if err := a.AuthSessions.Delete(ctx, authSession); err != nil {
113 a.Logger.Errorf("Error deleting auth session: %s", err)
114 return c.NoContent(http.StatusInternalServerError)
115 }
116
117 response, err := protocol.ParseCredentialCreationResponseBody(bytes.NewBuffer(body))
51 if err != nil { 118 if err != nil {
52 a.Logger.Errorf("Error parsing credential response: %s", err) 119 a.Logger.Errorf("Error parsing credential response: %s", err)
53 return c.NoContent(http.StatusBadRequest) 120 return c.NoContent(http.StatusBadRequest)
@@ -69,7 +136,7 @@ func (a *RegisterController[T]) HandleFinish(c echo.Context) error {
69 136
70 user.Fido2Credentials = append(user.Fido2Credentials, *credential) 137 user.Fido2Credentials = append(user.Fido2Credentials, *credential)
71 138
72 if err := a.Users.Upsert(c.Request().Context(), user); err != nil { 139 if err := a.Users.Upsert(ctx, user); err != nil {
73 a.Logger.Errorf("Error saving user: %s", err) 140 a.Logger.Errorf("Error saving user: %s", err)
74 return c.NoContent(http.StatusInternalServerError) 141 return c.NoContent(http.StatusInternalServerError)
75 } 142 }
diff --git a/cmd/web/server.go b/cmd/web/server.go
index 6eb585a..62624e2 100644
--- a/cmd/web/server.go
+++ b/cmd/web/server.go
@@ -32,14 +32,14 @@ import (
32 "github.com/spf13/cobra" 32 "github.com/spf13/cobra"
33) 33)
34 34
35func Register(root *cobra.Command, embeddedTemplates fs.FS, appVersion string) { 35func Register(root *cobra.Command, embeddedTemplates, embeddedClients fs.FS, appVersion string) {
36 webCmd := &cobra.Command{ 36 webCmd := &cobra.Command{
37 Use: "web [options]", 37 Use: "web [options]",
38 Short: "Run web server", 38 Short: "Run web server",
39 Run: func(c *cobra.Command, args []string) { 39 Run: func(c *cobra.Command, args []string) {
40 cfg := app.Config{} 40 cfg := app.Config{}
41 cli.MustGetConfig(c, &cfg) 41 cli.MustGetConfig(c, &cfg)
42 webMain(cfg, embeddedTemplates, appVersion) 42 webMain(cfg, embeddedTemplates, embeddedClients, appVersion)
43 }, 43 },
44 } 44 }
45 cli.AddFlags(webCmd, &app.Config{}, app.DefaultConfig, "web") 45 cli.AddFlags(webCmd, &app.Config{}, app.DefaultConfig, "web")
@@ -62,7 +62,7 @@ func PopulateTemplateContext(c echo.Context) (interface{}, error) {
62 }, nil 62 }, nil
63} 63}
64 64
65func webMain(cfg app.Config, embeddedTemplates fs.FS, appVersion string) { 65func webMain(cfg app.Config, embeddedTemplates, embeddedClients fs.FS, appVersion string) {
66 ctx, cancel := context.WithCancel(context.Background()) 66 ctx, cancel := context.WithCancel(context.Background())
67 defer cancel() 67 defer cancel()
68 68
@@ -221,6 +221,10 @@ func webMain(cfg app.Config, embeddedTemplates fs.FS, appVersion string) {
221 221
222 csm := glmiddleware.CSRFProtect(ss) 222 csm := glmiddleware.CSRFProtect(ss)
223 223
224 glecho.StaticFS(s.GET, embeddedClients, "/clients", "./clients/")
225
226 s.NeverCacheStaticRoute("/js", "js")
227
224 s.GET("/login", gt.Handle, csm) 228 s.GET("/login", gt.Handle, csm)
225 s.GET("/register", gt.Handle, csm) 229 s.GET("/register", gt.Handle, csm)
226 230
diff --git a/main.go b/main.go
index 70d0f48..7cc3e90 100644
--- a/main.go
+++ b/main.go
@@ -23,6 +23,9 @@ import (
23//go:embed templates 23//go:embed templates
24var embeddedTemplates embed.FS 24var embeddedTemplates embed.FS
25 25
26//go:embed clients
27var embeddedClients embed.FS
28
26var appVersion string 29var appVersion string
27 30
28func main() { 31func main() {
@@ -32,7 +35,7 @@ func main() {
32 } 35 }
33 cli.AddFlags(rootCmd, &app.Config{}, app.DefaultConfig, "") 36 cli.AddFlags(rootCmd, &app.Config{}, app.DefaultConfig, "")
34 37
35 web.Register(rootCmd, embeddedTemplates, appVersion) 38 web.Register(rootCmd, embeddedTemplates, embeddedClients, appVersion)
36 client.Register(rootCmd) 39 client.Register(rootCmd)
37 register.Register(rootCmd) 40 register.Register(rootCmd)
38 41
diff --git a/templates/js/base64.js b/templates/js/base64.js
new file mode 100644
index 0000000..52df92b
--- /dev/null
+++ b/templates/js/base64.js
@@ -0,0 +1,66 @@
1/*
2 * Base64URL-ArrayBuffer
3 * https://github.com/herrjemand/Base64URL-ArrayBuffer
4 *
5 * Copyright (c) 2017 Yuriy Ackermann <ackermann.yuriy@gmail.com>
6 * Copyright (c) 2012 Niklas von Hertzen
7 * Licensed under the MIT license.
8 *
9 */
10(function(){
11 'use strict';
12
13 let chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
14
15 // Use a lookup table to find the index.
16 let lookup = new Uint8Array(256);
17 for (let i = 0; i < chars.length; i++) {
18 lookup[chars.charCodeAt(i)] = i;
19 }
20
21 let encode = function(arraybuffer) {
22 let bytes = new Uint8Array(arraybuffer),
23 i, len = bytes.length, base64url = '';
24
25 for (i = 0; i < len; i+=3) {
26 base64url += chars[bytes[i] >> 2];
27 base64url += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
28 base64url += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
29 base64url += chars[bytes[i + 2] & 63];
30 }
31
32 if ((len % 3) === 2) {
33 base64url = base64url.substring(0, base64url.length - 1);
34 } else if (len % 3 === 1) {
35 base64url = base64url.substring(0, base64url.length - 2);
36 }
37
38 return base64url;
39 };
40
41 let decode = function(base64string) {
42 let bufferLength = base64string.length * 0.75,
43 len = base64string.length, i, p = 0,
44 encoded1, encoded2, encoded3, encoded4;
45
46 let bytes = new Uint8Array(bufferLength);
47
48 for (i = 0; i < len; i+=4) {
49 encoded1 = lookup[base64string.charCodeAt(i)];
50 encoded2 = lookup[base64string.charCodeAt(i+1)];
51 encoded3 = lookup[base64string.charCodeAt(i+2)];
52 encoded4 = lookup[base64string.charCodeAt(i+3)];
53
54 bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
55 bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
56 bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
57 }
58
59 return bytes.buffer
60 };
61
62 window.base64url = {
63 'decode': decode,
64 'encode': encode
65 };
66})();
diff --git a/templates/login.tpl b/templates/login.tpl
index 0323409..7ee357d 100644
--- a/templates/login.tpl
+++ b/templates/login.tpl
@@ -4,76 +4,10 @@
4 <meta charset="utf-8" /> 4 <meta charset="utf-8" />
5 <meta name="viewport" content="width=device-width, initial-scale=1"> 5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="render-time" content="{{ .RenderTime }}"> 6 <meta name="render-time" content="{{ .RenderTime }}">
7 <meta name="csrf-token" content="{{ .CSRFToken }}" />
7 {{ if .Context.HasKey "title" }}<title>{{ .Context.Get "title" }}</title>{{ else }}<title>SSH Proxy</title>{{ end }} 8 {{ if .Context.HasKey "title" }}<title>{{ .Context.Get "title" }}</title>{{ else }}<title>SSH Proxy</title>{{ end }}
8 9
9 <script type="text/javascript"> 10 <script type="text/javascript" src="/js/base64.js"></script>
10 /*
11 * Base64URL-ArrayBuffer
12 * https://github.com/herrjemand/Base64URL-ArrayBuffer
13 *
14 * Copyright (c) 2017 Yuriy Ackermann <ackermann.yuriy@gmail.com>
15 * Copyright (c) 2012 Niklas von Hertzen
16 * Licensed under the MIT license.
17 *
18 */
19 (function(){
20 'use strict';
21
22 let chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
23
24 // Use a lookup table to find the index.
25 let lookup = new Uint8Array(256);
26 for (let i = 0; i < chars.length; i++) {
27 lookup[chars.charCodeAt(i)] = i;
28 }
29
30 let encode = function(arraybuffer) {
31 let bytes = new Uint8Array(arraybuffer),
32 i, len = bytes.length, base64url = '';
33
34 for (i = 0; i < len; i+=3) {
35 base64url += chars[bytes[i] >> 2];
36 base64url += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)];
37 base64url += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
38 base64url += chars[bytes[i + 2] & 63];
39 }
40
41 if ((len % 3) === 2) {
42 base64url = base64url.substring(0, base64url.length - 1);
43 } else if (len % 3 === 1) {
44 base64url = base64url.substring(0, base64url.length - 2);
45 }
46
47 return base64url;
48 };
49
50 let decode = function(base64string) {
51 let bufferLength = base64string.length * 0.75,
52 len = base64string.length, i, p = 0,
53 encoded1, encoded2, encoded3, encoded4;
54
55 let bytes = new Uint8Array(bufferLength);
56
57 for (i = 0; i < len; i+=4) {
58 encoded1 = lookup[base64string.charCodeAt(i)];
59 encoded2 = lookup[base64string.charCodeAt(i+1)];
60 encoded3 = lookup[base64string.charCodeAt(i+2)];
61 encoded4 = lookup[base64string.charCodeAt(i+3)];
62
63 bytes[p++] = (encoded1 << 2) | (encoded2 >> 4);
64 bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
65 bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
66 }
67
68 return bytes.buffer
69 };
70
71 window.base64url = {
72 'decode': decode,
73 'encode': encode
74 };
75 })();
76 </script>
77 11
78 <script type="text/javascript"> 12 <script type="text/javascript">
79 window.addEventListener("load", _ => { 13 window.addEventListener("load", _ => {
@@ -83,24 +17,35 @@
83 document.getElementById("code").value = code; 17 document.getElementById("code").value = code;
84 } 18 }
85 19
20 const usernameCookie = document.cookie
21 .split("; ")
22 .find((row) => row.startsWith("username="))
23 ?.split("=")[1];
24
25 if (usernameCookie != undefined && usernameCookie !== "") {
26 document.getElementById("username").value = usernameCookie;
27 }
28
86 document.getElementById("login").addEventListener("click", evt => { 29 document.getElementById("login").addEventListener("click", evt => {
87 evt.preventDefault(); 30 evt.preventDefault();
88 31
89 var username = document.getElementById("username"); 32 const username = document.getElementById("username").value;
90 fetch("/auth/login/" + username.value) 33 document.cookie = `username=${username}; expires=Fri, 31 Dec 9999 23:59:59 GMT; Secure`;
91 .then((result) => result.json()) 34
35 fetch("/auth/login/" + username)
36 .then((response) => response.json())
92 .then((data) => { 37 .then((data) => {
93 data.publicKey.challenge = base64url.decode(data.publicKey.challenge); 38 data.publicKey.challenge = base64url.decode(data.publicKey.challenge);
94 data.publicKey.allowCredentials.forEach(e => e.id = base64url.decode(e.id)); 39 data.publicKey.allowCredentials.forEach(e => e.id = base64url.decode(e.id));
95 40
96 navigator.credentials.get(data) 41 navigator.credentials.get(data)
97 .then((credential) => { 42 .then((credential) => {
98 fetch("/auth/login/" + username.value, { 43 fetch("/auth/login/" + username, {
99 method: "POST", 44 method: "POST",
100 mode: "same-origin", 45 mode: "same-origin",
101 headers: { 46 headers: {
102 "Content-Type": "application/json", 47 "Content-Type": "application/json",
103 "X-CSRF-Token": "{{ .CSRFToken }}" 48 "X-CSRF-Token": document.querySelector("meta[name=csrf-token]").content,
104 }, 49 },
105 body: JSON.stringify({ 50 body: JSON.stringify({
106 code: document.getElementById("code").value, 51 code: document.getElementById("code").value,
@@ -130,7 +75,6 @@
130 <form> 75 <form>
131 <label for="code">Code: <input type="text" name="code" id="code" /></label><br/> 76 <label for="code">Code: <input type="text" name="code" id="code" /></label><br/>
132 <label for="username">Username: <input type="text" name="username" id="username" autocorrect="off" autocapitalize="none" autocomplete="username" /></label><br/> 77 <label for="username">Username: <input type="text" name="username" id="username" autocorrect="off" autocapitalize="none" autocomplete="username" /></label><br/>
133 <input type="hidden" value="{{ .CSRFToken }}" name="csrf-token" />
134 <input type="submit" id="login" value="Login" /> 78 <input type="submit" id="login" value="Login" />
135 </form> 79 </form>
136 </body> 80 </body>
diff --git a/templates/register.tpl b/templates/register.tpl
index 794ddaa..37252a0 100644
--- a/templates/register.tpl
+++ b/templates/register.tpl
@@ -4,143 +4,85 @@
4 <meta charset="utf-8" /> 4 <meta charset="utf-8" />
5 <meta name="viewport" content="width=device-width, initial-scale=1"> 5 <meta name="viewport" content="width=device-width, initial-scale=1">
6 <meta name="render-time" content="{{ .RenderTime }}"> 6 <meta name="render-time" content="{{ .RenderTime }}">
7 <meta name="csrf-token" content="{{ .CSRFToken }}" />
7 {{ if .Context.HasKey "title" }}<title>{{ .Context.Get "title" }}</title>{{ else }}<title>SSH Proxy</title>{{ end }} 8 {{ if .Context.HasKey "title" }}<title>{{ .Context.Get "title" }}</title>{{ else }}<title>SSH Proxy</title>{{ end }}
8 </head>
9
10 <body>
11 <script type="text/javascript">
12 /*
13 * Base64URL-ArrayBuffer
14 * https://github.com/herrjemand/Base64URL-ArrayBuffer
15 *
16 * Copyright (c) 2017 Yuriy Ackermann <ackermann.yuriy@gmail.com>
17 * Copyright (c) 2012 Niklas von Hertzen
18 * Licensed under the MIT license.
19 *
20 */
21 (function(){
22 'use strict';
23
24 let chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_';
25
26 // Use a lookup table to find the index.
27 let lookup = new Uint8Array(256);
28 for (let i = 0; i < chars.length; i++) {
29 lookup[chars.charCodeAt(i)] = i;
30 }
31 9
32 let encode = function(arraybuffer) { 10 <script type="text/javascript" src="/js/base64.js"></script>
33 let bytes = new Uint8Array(arraybuffer),
34 i, len = bytes.length, base64url = '';
35 11
36 for (i = 0; i < len; i+=3) { 12 <script type="text/javascript">
37 base64url += chars[bytes[i] >> 2]; 13 function doRegister(evt) {
38 base64url += chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)]; 14 evt.preventDefault();
39 base64url += chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)];
40 base64url += chars[bytes[i + 2] & 63];
41 }
42
43 if ((len % 3) === 2) {
44 base64url = base64url.substring(0, base64url.length - 1);
45 } else if (len % 3 === 1) {
46 base64url = base64url.substring(0, base64url.length - 2);
47 }
48
49 return base64url;
50 };
51
52 let decode = function(base64string) {
53 let bufferLength = base64string.length * 0.75,
54 len = base64string.length, i, p = 0,
55 encoded1, encoded2, encoded3, encoded4;
56
57 let bytes = new Uint8Array(bufferLength);
58
59 for (i = 0; i < len; i+=4) {
60 encoded1 = lookup[base64string.charCodeAt(i)];
61 encoded2 = lookup[base64string.charCodeAt(i+1)];
62 encoded3 = lookup[base64string.charCodeAt(i+2)];
63 encoded4 = lookup[base64string.charCodeAt(i+3)];
64 15
65 bytes[p++] = (encoded1 << 2) | (encoded2 >> 4); 16 const code = document.getElementById("code").value;
66 bytes[p++] = ((encoded2 & 15) << 4) | (encoded3 >> 2);
67 bytes[p++] = ((encoded3 & 3) << 6) | (encoded4 & 63);
68 }
69 17
70 return bytes.buffer 18 const username = document.getElementById("username").value;
71 }; 19 document.cookie = `username=${username}; expires=Fri, 31 Dec 9999 23:59:59 GMT; Secure`;
72 20
73 window.base64url = { 21 fetch(`/auth/register/${username}?code=${code}`)
74 'decode': decode, 22 .then((response) => {
75 'encode': encode 23 if (!response.ok) {
76 }; 24 document.body.innerHTML = "<h1>Error Fetching Registration Request</h1>";
77 })(); 25 throw new Error("Error fetching registration request");
26 }
27 return response.json();
28 })
29 .then((data) => {
30 data.publicKey.challenge = base64url.decode(data.publicKey.challenge);
31 data.publicKey.user.id = base64url.decode(data.publicKey.user.id);
32
33 navigator.credentials.create(data)
34 .then((credential) => {
35 fetch(`/auth/register/${username}`, {
36 method: "POST",
37 mode: "same-origin",
38 headers: {
39 "Content-Type": "application/json",
40 "X-CSRF-Token": document.querySelector("meta[name=csrf-token]").content,
41 },
42 body: JSON.stringify({
43 code: code,
44 type: credential.type,
45 id: credential.id,
46 rawId: base64url.encode(credential.rawId),
47 response: {
48 clientDataJSON: base64url.encode(credential.response.clientDataJSON),
49 attestationObject: base64url.encode(credential.response.attestationObject)
50 }
51 })
52 })
53 .then((response) => {
54 if (response.ok) { document.body.innerHTML = "<h1>Success</h1>"; }
55 else { document.body.innerHTML = "<h1>Failure</h1>"; }
56 });
57 });
58 });
59 }
78 60
79 var request = {{ .Model.WebautnRequest }}; 61 window.addEventListener("load", _ => {
80 request.publicKey.challenge = base64url.decode(request.publicKey.challenge); 62 const urlParams = new URLSearchParams(window.location.search);
81 {{ if .Model.LoginMode }} 63 const code = urlParams.get("code");
82 request.publicKey.allowCredentials.forEach(e => e.id = base64url.decode(e.id)); 64 if (code !== "") {
83 {{ else }} 65 document.getElementById("code").value = code;
84 request.publicKey.user.id = base64url.decode(request.publicKey.user.id); 66 }
85 {{ end }}
86 67
87 {{ if .Model.LoginMode }} 68 const usernameCookie = document.cookie.split("; ")
88 navigator.credentials.get(request) 69 .find((row) => row.startsWith("username="))
89 .then((credential) => { 70 .split("=")[1];
90 console.log(credential);
91 71
92 fetch(document.URL, { 72 if (usernameCookie != undefined && usernameCookie !== "") {
93 method: "POST", 73 document.getElementById("username").value = usernameCookie;
94 mode: "same-origin", 74 }
95 headers: {
96 "Content-Type": "application/json",
97 "X-CSRF-Token": "{{ .CSRFToken }}"
98 },
99 body: JSON.stringify({
100 type: credential.type,
101 id: credential.id,
102 rawId: base64url.encode(credential.rawId),
103 response: {
104 authenticatorData: base64url.encode(credential.response.authenticatorData),
105 clientDataJSON: base64url.encode(credential.response.clientDataJSON),
106 signature: base64url.encode(credential.response.signature),
107 userHandle: base64url.encode(credential.response.userHandle)
108 }
109 })
110 })
111 .then((response) => {
112 if (response.ok) { document.body.innerHTML = "<h1>Success</h1>"; }
113 else { document.body.innerHTML = "<h1>Failure</h1>"; }
114 });
115 });
116 {{ else }}
117 navigator.credentials.create(request)
118 .then((credential) => {
119 console.log(credential);
120 75
121 fetch(document.URL, { 76 document.getElementById("login").addEventListener("click", doRegister);
122 method: "POST", 77 });
123 mode: "same-origin",
124 headers: {
125 "Content-Type": "application/json",
126 "X-CSRF-Token": "{{ .CSRFToken }}"
127 },
128 body: JSON.stringify({
129 type: credential.type,
130 id: credential.id,
131 rawId: base64url.encode(credential.rawId),
132 response: {
133 clientDataJSON: base64url.encode(credential.response.clientDataJSON),
134 attestationObject: base64url.encode(credential.response.attestationObject)
135 }
136 })
137 })
138 .then((response) => {
139 if (response.ok) { document.body.innerHTML = "<h1>Success</h1>"; }
140 else { document.body.innerHTML = "<h1>Failure</h1>"; }
141 });
142 });
143 {{ end }}
144 </script> 78 </script>
79 </head>
80
81 <body>
82 <form>
83 <label for="code">Code: <input type="text" name="code" id="code" /></label><br/>
84 <label for="username">Username: <input type="text" name="username" id="username" autocorrect="off" autocapitalize="none" autocomplete="username" /></label><br/>
85 <input type="submit" id="login" value="Login" />
86 </form>
145 </body> 87 </body>
146</html> 88</html>