oficina / home Public

Use required component attributes in credential templates

Paul Campbell pushed by paul@keymail.dev eb6e73970c995d80f6f63907fc61c327ed32cb83 parent 1c8274d
7 files changed, +56 −23
  • internal/home/markup_test.go +33 −0
  • internal/home/templates/layout.html +1 −1
  • internal/home/templates/passkey_confirm.html +8 −8
  • internal/home/templates/passkey_enrol.html +2 −2
  • internal/home/templates/recovery_codes.html +2 −2
  • internal/home/templates/signin.html +7 −7
  • internal/home/templates/signin_confirm.html +3 −3

CI passed — run details

diff --git a/internal/home/markup_test.go b/internal/home/markup_test.go
new file mode 100644
index 0000000..c010d36
--- /dev/null
+++ b/internal/home/markup_test.go
@@ -0,0 +1,33 @@
+package home
+
+import (
+ "os"
+ "path/filepath"
+ "regexp"
+ "strings"
+ "testing"
+)
+
+func TestTemplateComponentSpelling(t *testing.T) {
+ allowed := map[string]bool{"rst-sr-only": true, "rst-mono": true, "rst-m-hide": true, "rst-grow": true, "rst-nm": true, "rst-danger": true, "rst-cell-mut": true}
+ classes := regexp.MustCompile(`(?i)(?:^|\s)class\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))`)
+ paths, err := filepath.Glob("templates/*.html")
+ if err != nil || len(paths) == 0 {
+ t.Fatalf("template enumeration: %v", err)
+ }
+ for _, path := range paths {
+ data, err := os.ReadFile(path)
+ if err != nil {
+ t.Fatal(err)
+ }
+ for _, match := range classes.FindAllStringSubmatch(string(data), -1) {
+ for _, value := range match[1:] {
+ for _, class := range strings.Fields(value) {
+ if strings.HasPrefix(class, "rst-") && !allowed[class] {
+ t.Errorf("%s: component class %q must use attribute spelling", path, class)
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/internal/home/templates/layout.html b/internal/home/templates/layout.html
index 2275b90..9340be0 100644
--- a/internal/home/templates/layout.html
+++ b/internal/home/templates/layout.html
@@ -8,7 +8,7 @@
<link rel="stylesheet" href="/static/theme.css">
</head>
<body{{block "bodyattrs" .}}{{end}}>
-<main class="rst-page">
+<main rst-page>
{{template "content" .}}
</main>
{{block "scripts" .}}{{end}}
diff --git a/internal/home/templates/passkey_confirm.html b/internal/home/templates/passkey_confirm.html
index 90d6f40..e6de3f2 100644
--- a/internal/home/templates/passkey_confirm.html
+++ b/internal/home/templates/passkey_confirm.html
@@ -3,21 +3,21 @@
{{define "content"}}
<h1>Confirm it is you</h1>
-{{if .RecoveryFailed}}<p role="alert" class="rst-callout" data-tone="negative"><span class="rst-callout__body">That code did not work.</span></p>{{end}}
+{{if .RecoveryFailed}}<p role="alert" rst-callout rst-tone="negative"><span rst-callout-body>That code did not work.</span></p>{{end}}
-<button id="passkey-confirm" type="button" class="rst-btn rst-btn--primary" data-passkey hidden>Use your passkey</button>
+<button id="passkey-confirm" type="button" rst-btn="primary" data-passkey hidden>Use your passkey</button>
<p id="passkey-status" role="status"></p>
<h2>Or use a recovery code</h2>
<p>Recovery is exactly the moment a passkey is not working, so this form
needs no script and no device.</p>
-<form method="post" action="/passkey/signin/recovery" class="rst-form">
- <div class="rst-field">
- <label class="rst-field__label" for="code">Recovery code</label>
- <input id="code" name="code" type="text" autocomplete="one-time-code" required class="rst-input">
+<form method="post" action="/passkey/signin/recovery" rst-form>
+ <div rst-field>
+ <label rst-field-label for="code">Recovery code</label>
+ <input id="code" name="code" type="text" autocomplete="one-time-code" required rst-input>
</div>
- <div class="rst-form-foot">
- <button type="submit" class="rst-btn rst-btn--primary">Sign in</button>
+ <div rst-form-foot>
+ <button type="submit" rst-btn="primary">Sign in</button>
</div>
</form>
{{end}}
diff --git a/internal/home/templates/passkey_enrol.html b/internal/home/templates/passkey_enrol.html
index a57d3df..a2edcb5 100644
--- a/internal/home/templates/passkey_enrol.html
+++ b/internal/home/templates/passkey_enrol.html
@@ -4,8 +4,8 @@
<h1>Add a passkey</h1>
<p>A passkey is an optional second step. Without one, your email link is
the only thing standing between someone with your inbox and your account.</p>
-<button id="passkey-enrol" type="button" class="rst-btn rst-btn--primary" data-passkey hidden>Add a passkey</button>
+<button id="passkey-enrol" type="button" rst-btn="primary" data-passkey hidden>Add a passkey</button>
<p id="passkey-status" role="status"></p>
-<noscript><p class="rst-callout"><span class="rst-callout__body">Adding a passkey needs JavaScript. Everything else here does not.</span></p></noscript>
+<noscript><p rst-callout><span rst-callout-body>Adding a passkey needs JavaScript. Everything else here does not.</span></p></noscript>
{{end}}
{{define "scripts"}}<script type="module" src="/static/passkey.js"></script>{{end}}
diff --git a/internal/home/templates/recovery_codes.html b/internal/home/templates/recovery_codes.html
index 90c7698..d88ca24 100644
--- a/internal/home/templates/recovery_codes.html
+++ b/internal/home/templates/recovery_codes.html
@@ -2,7 +2,7 @@
{{define "content"}}
<h1>Recovery codes</h1>
{{if .Codes}}
-<p class="rst-callout" data-tone="warning"><span class="rst-callout__body"><strong>These are shown once.</strong> Keep them somewhere your
+<p rst-callout rst-tone="warning"><span rst-callout-body><strong>These are shown once.</strong> Keep them somewhere your
passkey is not. Each one works a single time.</span></p>
<ul>{{range .Codes}}<li><code>{{.}}</code></li>{{end}}</ul>
<p>Losing your passkey and these codes together locks you out permanently.
@@ -11,7 +11,7 @@ There is no operator who can undo that, by design.</p>
<p>You have {{.Remaining}} unused recovery codes.</p>
<p>Generating a new set <strong>invalidates every code you have now</strong>.</p>
<form method="post" action="/passkey/codes">
- <button type="submit" class="rst-btn rst-btn--danger">Generate a new set</button>
+ <button type="submit" rst-btn="danger">Generate a new set</button>
</form>
{{end}}
{{end}}
diff --git a/internal/home/templates/signin.html b/internal/home/templates/signin.html
index 6a81536..94b0e20 100644
--- a/internal/home/templates/signin.html
+++ b/internal/home/templates/signin.html
@@ -1,14 +1,14 @@
{{define "title"}}Sign in · Oficina{{end}}
{{define "content"}}
<h1>Sign in</h1>
-{{if .Error}}<p role="alert" class="rst-callout" data-tone="negative"><span class="rst-callout__body">{{.Error}}</span></p>{{end}}
-<form method="post" action="/signin" class="rst-form">
- <div class="rst-field">
- <label class="rst-field__label" for="address">Email address</label>
- <input id="address" name="address" type="email" autocomplete="email" required autofocus class="rst-input">
+{{if .Error}}<p role="alert" rst-callout rst-tone="negative"><span rst-callout-body>{{.Error}}</span></p>{{end}}
+<form method="post" action="/signin" rst-form>
+ <div rst-field>
+ <label rst-field-label for="address">Email address</label>
+ <input id="address" name="address" type="email" autocomplete="email" required autofocus rst-input>
</div>
- <div class="rst-form-foot">
- <button type="submit" class="rst-btn rst-btn--primary">Continue</button>
+ <div rst-form-foot>
+ <button type="submit" rst-btn="primary">Continue</button>
</div>
</form>
{{end}}
diff --git a/internal/home/templates/signin_confirm.html b/internal/home/templates/signin_confirm.html
index 9446f4d..b27fe86 100644
--- a/internal/home/templates/signin_confirm.html
+++ b/internal/home/templates/signin_confirm.html
@@ -2,10 +2,10 @@
{{define "content"}}
<h1>Finish signing in</h1>
<p>Press the button to sign in on this device.</p>
-<form method="post" action="/signin/verify" class="rst-form">
+<form method="post" action="/signin/verify" rst-form>
<input type="hidden" name="token" value="{{.Token}}">
- <div class="rst-form-foot">
- <button type="submit" class="rst-btn rst-btn--primary">Sign in</button>
+ <div rst-form-foot>
+ <button type="submit" rst-btn="primary">Sign in</button>
</div>
</form>
{{end}}