Skip to content

Commit

Permalink
Reimplement passkey login with htmx instead of solid
Browse files Browse the repository at this point in the history
  • Loading branch information
foodelevator committed Aug 18, 2024
1 parent 8ac7a88 commit b42539b
Show file tree
Hide file tree
Showing 9 changed files with 246 additions and 136 deletions.
97 changes: 0 additions & 97 deletions islands/passkeyLogin.island.tsx

This file was deleted.

1 change: 1 addition & 0 deletions services/passkey/export/passkey.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
)

type Service interface {
PasskeyLogin() func() templ.Component
PasskeySettings(ctx context.Context, kthid string) (func() templ.Component, error)
}

Expand Down
17 changes: 7 additions & 10 deletions services/passkey/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package passkey

import (
"encoding/json"
"io"
"net/http"
"time"

Expand All @@ -17,16 +16,14 @@ import (
var hackSession *webauthn.SessionData

func (s *service) beginLoginPasskey(w http.ResponseWriter, r *http.Request) httputil.ToResponse {
kthid, err := io.ReadAll(r.Body)
if err != nil {
return err
}
user, err := s.user.GetUser(r.Context(), string(kthid))
kthid := r.FormValue("kthid")
user, err := s.user.GetUser(r.Context(), kthid)
if err != nil {
return err
}
if user == nil {
return httputil.BadRequest("No such user")
w.Header().Add("HX-Reswap", "beforeend")
return `<p class="error">No such user</p>`
}
passkeys, err := s.listPasskeysForUser(r.Context(), user.KTHID)
if len(passkeys) == 0 {
Expand All @@ -37,18 +34,18 @@ func (s *service) beginLoginPasskey(w http.ResponseWriter, r *http.Request) http
if err != nil {
return err
}
return httputil.JSON(credAss)
return passkeyLogin(kthid, credAss)
}

func (s *service) finishLoginPasskey(w http.ResponseWriter, r *http.Request) httputil.ToResponse {
var body struct {
KTHID string `json:"kthid"`
protocol.CredentialAssertionResponse
Cred protocol.CredentialAssertionResponse `json:"cred"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
return httputil.BadRequest("Invalid credential")
}
credAss, err := body.CredentialAssertionResponse.Parse()
credAss, err := body.Cred.Parse()
if err != nil {
return httputil.BadRequest("Invalid credential")
}
Expand Down
4 changes: 4 additions & 0 deletions services/passkey/passkey.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ func (s *service) listPasskeysForUser(ctx context.Context, kthid string) ([]expo
return passkeys, nil
}

func (s *service) PasskeyLogin() func() templ.Component {
return func() templ.Component { return passkeyLogin("", nil) }
}

func (s *service) PasskeySettings(ctx context.Context, kthid string) (func() templ.Component, error) {
passkeys, err := s.listPasskeysForUser(ctx, kthid)
if err != nil {
Expand Down
96 changes: 96 additions & 0 deletions services/passkey/passkey.templ
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,101 @@ import (
"github.com/go-webauthn/webauthn/protocol"
)

func bigIfTrue[T any](cond bool, thenVal, elseVal T) T {
if cond {
return thenVal
} else {
return elseVal
}
}

templ passkeyLogin(kthid string, credAss *protocol.CredentialAssertion) {
<form
id="passkey-login-form"
hx-post="/login/passkey/begin"
hx-on::before-request="this.querySelectorAll('.error').forEach(e => e.remove())"
if credAss != nil {
data-cred-ass={templ.JSONString(credAss)}
}
hx-swap="outerHTML"
class="[&>.error]:bg-red-600/50 [&>.error]:p-2 [&>.error]:mt-2 [&>.error]:rounded"
>
if credAss != nil {
<script type="module">
let form = document.querySelector("#passkey-login-form");
let credAss = JSON.parse(form.dataset.credAss);
credAss.publicKey.challenge = decodebase64url(credAss.publicKey.challenge);
for (let ac of credAss.publicKey.allowCredentials) {
ac.id = decodebase64url(ac.id);
}
console.log(credAss);
event.preventDefault();
try {
let cred = await navigator.credentials.get(credAss);
let res = await fetch("/login/passkey/finish", {
method: "post",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
kthid: new FormData(form).get("kthid"),
cred: {
id: cred.id,
rawId: encodebase64url(cred.rawId),
type: cred.type,
authenticatorAttachment: cred.authenticatorAttachment,
response: {
authenticatorData: encodebase64url(cred.response.authenticatorData),
clientDataJSON: encodebase64url(cred.response.clientDataJSON),
signature: encodebase64url(cred.response.signature),
userHandle: encodebase64url(cred.response.userHandle),
},
},
}),
});
if (res.status == 200)
window.location.replace("/");
else
throw new Error(await res.text());
} catch (err) {
let text = (err.name === "NotAllowedError")
? "Missing permission or request was cancelled"
: err.message;
let el = document.createElement("p");
el.classList.add("error");
el.textContent = text;
form.appendChild(el);
} finally {
form.querySelector("button").classList.remove("spinner");
}
</script>
}
<label class="text-sm" for="pk-kthid">Log In using a Passkey</label>
<div class="flex gap-2">
<input
id="pk-kthid"
name="kthid"
type="text"
required
placeholder="KTH ID"
value={ kthid }
class="
border border-neutral-500 grow
outline-none focus:border-cerise-strong hover:border-cerise-light
bg-slate-800 p-1.5 rounded h-8
"
/>
<button
class={ `
bg-[#3f4c66] shrink-0 h-8 w-8 rounded-full
grid place-items-center pointer
border border-transparent outline-none focus:border-cerise-strong hover:border-cerise-light relative
`+bigIfTrue(credAss != nil, "spinner", "") }
>
<img src="/public/key_icon.svg" class="w-3/5 h-3/5 invert"/>
</button>
</div>
</form>
}

templ showPasskey(passkey export.Passkey) {
<li class="flex p-2 gap-2 items-center">
<span>{ passkey.Name }</span>
Expand Down Expand Up @@ -49,6 +144,7 @@ templ addPasskeyForm(cc *protocol.CredentialCreation) {
<form
data-credential-creation={ templ.JSONString(cc) }
onsubmit="addPasskey(this, event)"
class="[&>.error]:bg-red-600/50 [&>.error]:p-2 [&>.error]:mt-2 [&>.error]:rounded"
>
<script>
async function addPasskey(form, event) {
Expand Down
Loading

0 comments on commit b42539b

Please sign in to comment.