+1
-1
who-am-i/src/server.rs
+1
-1
who-am-i/src/server.rs
+1
-1
who-am-i/static/style.css
+1
-1
who-am-i/static/style.css
-167
who-am-i/templates/prompt-base.hbs
-167
who-am-i/templates/prompt-base.hbs
···
1
-
<!doctype html>
2
-
3
-
<style>
4
-
body {
5
-
color: #434;
6
-
font-family: 'Iowan Old Style', 'Palatino Linotype', 'URW Palladio L', P052, serif;
7
-
margin: 0;
8
-
min-height: 100vh;
9
-
padding: 0;
10
-
}
11
-
.wrap {
12
-
border: 2px solid #221828;
13
-
border-radius: 0.5rem;
14
-
box-sizing: border-box;
15
-
overflow: hidden;
16
-
display: flex;
17
-
flex-direction: column;
18
-
height: 100vh;
19
-
}
20
-
header {
21
-
background: #221828;
22
-
display: flex;
23
-
justify-content: space-between;
24
-
padding: 0 0.25rem;
25
-
color: #c9b;
26
-
display: flex;
27
-
gap: 0.5rem;
28
-
align-items: baseline;
29
-
}
30
-
header > * {
31
-
flex-basis: 33%;
32
-
}
33
-
header > .empty {
34
-
font-size: 0.8rem;
35
-
opacity: 0.5;
36
-
}
37
-
header > .title {
38
-
text-align: center;
39
-
}
40
-
header > a.micro {
41
-
text-decoration: none;
42
-
font-size: 0.8rem;
43
-
text-align: right;
44
-
opacity: 0.5;
45
-
}
46
-
header > a.micro:hover {
47
-
opacity: 1;
48
-
}
49
-
main {
50
-
background: #ccc;
51
-
display: flex;
52
-
flex-direction: column;
53
-
flex-grow: 1;
54
-
padding: 0.25rem 0.5rem;
55
-
}
56
-
p {
57
-
margin: 1rem 0 0;
58
-
text-align: center;
59
-
}
60
-
p.detail {
61
-
font-size: 0.8rem;
62
-
}
63
-
.parent-host {
64
-
font-weight: bold;
65
-
color: #48c;
66
-
display: inline-block;
67
-
padding: 0 0.125rem;
68
-
border-radius: 0.25rem;
69
-
border: 1px solid #aaa;
70
-
font-size: 0.8rem;
71
-
}
72
-
73
-
#loader {
74
-
display: flex;
75
-
flex-grow: 1;
76
-
justify-content: center;
77
-
align-items: center;
78
-
}
79
-
.spinner {
80
-
animation: rotation 1.618s ease-in-out infinite;
81
-
border-radius: 50%;
82
-
border: 3px dashed #434;
83
-
box-sizing: border-box;
84
-
display: inline-block;
85
-
height: 1.5em;
86
-
width: 1.5em;
87
-
}
88
-
@keyframes rotation {
89
-
0% { transform: rotate(0deg) }
90
-
100% { transform: rotate(360deg) }
91
-
}
92
-
93
-
#user-info {
94
-
flex-grow: 1;
95
-
display: flex;
96
-
flex-direction: column;
97
-
justify-content: center;
98
-
}
99
-
#action {
100
-
background: #eee;
101
-
display: flex;
102
-
justify-content: space-between;
103
-
padding: 0.5rem 0.25rem 0.5rem 0.5rem;
104
-
font-size: 0.8rem;
105
-
align-items: baseline;
106
-
border-radius: 0.5rem;
107
-
border: 1px solid #bbb;
108
-
cursor: pointer;
109
-
}
110
-
#action:hover {
111
-
background: #fff;
112
-
}
113
-
#allow {
114
-
background: transparent;
115
-
border: none;
116
-
border-left: 1px solid #bbb;
117
-
padding: 0 0.5rem;
118
-
color: #375;
119
-
font: inherit;
120
-
cursor: pointer;
121
-
}
122
-
#action:hover #allow {
123
-
color: #285;
124
-
}
125
-
126
-
#or {
127
-
font-size: 0.8rem;
128
-
text-align: center;
129
-
}
130
-
#or p {
131
-
margin: 0 0 1rem;
132
-
}
133
-
134
-
input#handle {
135
-
border: none;
136
-
border-bottom: 1px dashed #aaa;
137
-
background: transparent;
138
-
}
139
-
140
-
.hidden {
141
-
display: none !important;
142
-
}
143
-
144
-
</style>
145
-
146
-
<div class="wrap">
147
-
<header>
148
-
<div class="empty">🔒</div>
149
-
<code class="title" style="font-family: monospace;"
150
-
>who-am-i</code>
151
-
<a href="https://microcosm.blue" target="_blank" class="micro"
152
-
><span style="color: #f396a9">m</span
153
-
><span style="color: #f49c5c">i</span
154
-
><span style="color: #c7b04c">c</span
155
-
><span style="color: #92be4c">r</span
156
-
><span style="color: #4ec688">o</span
157
-
><span style="color: #51c2b6">c</span
158
-
><span style="color: #54bed7">o</span
159
-
><span style="color: #8fb1f1">s</span
160
-
><span style="color: #ce9df1">m</span
161
-
></a>
162
-
</header>
163
-
164
-
<main>
165
-
{{> main}}
166
-
</main>
167
-
</div>
-59
who-am-i/templates/prompt-known.hbs
-59
who-am-i/templates/prompt-known.hbs
···
1
-
{{#*inline "main"}}
2
-
<p>
3
-
Share your identity with
4
-
<span class="parent-host">{{ parent_host }}</span>?
5
-
</p>
6
-
<div id="loader">
7
-
<span class="spinner"></span>
8
-
</div>
9
-
<div id="user-info" class="hidden">
10
-
<div id="action">
11
-
<span id="handle"></span>
12
-
<button id="allow">Allow</button>
13
-
</div>
14
-
</div>
15
-
<div id="or">
16
-
<p>or, <a id="switch" href="#">use another account</a></p>
17
-
</div>
18
-
19
-
<script>
20
-
var loaderEl = document.getElementById('loader');
21
-
var infoEl = document.getElementById('user-info');
22
-
var actionEl = document.getElementById('action');
23
-
var handleEl = document.getElementById('handle');
24
-
var allowEl = document.getElementById('allow');
25
-
var switchEl = document.getElementById('switch');
26
-
27
-
switchEl.addEventListener('click', e => {
28
-
e.preventDefault();
29
-
console.log('switch plz');
30
-
});
31
-
32
-
var DID = {{{json did}}};
33
-
let user_info = new URL('/user-info', window.location);
34
-
user_info.searchParams.set('fetch-key', {{{json fetch_key}}});
35
-
fetch(user_info)
36
-
.then(resp => {
37
-
if (!resp.ok) throw new Error('request failed');
38
-
return resp.json();
39
-
})
40
-
.then(
41
-
({ handle }) => {
42
-
loaderEl.remove();
43
-
handleEl.textContent = `@${handle}`;
44
-
infoEl.classList.remove('hidden');
45
-
actionEl.addEventListener('click', () => share(handle));
46
-
},
47
-
err => {
48
-
infoEl.textContent = 'ohno';
49
-
console.error(err);
50
-
},
51
-
);
52
-
53
-
function share(handle) {
54
-
top.postMessage({ source: 'whoami', handle }, '*'); // TODO: pass the referrer back from server
55
-
}
56
-
</script>
57
-
{{/inline}}
58
-
59
-
{{#> prompt-base}}{{/prompt-base}}
+23
-10
who-am-i/templates/prompt.hbs
+23
-10
who-am-i/templates/prompt.hbs
···
16
16
<div id="user-info">
17
17
<form id="form-action" action="/auth" method="GET" target="_blank" class="action {{#if did}}hidden{{/if}}">
18
18
<label>
19
-
@<input id="handle" name="handle" placeholder="example.bsky.social" />
19
+
@<input id="handle-input" class="handle" name="handle" placeholder="example.bsky.social" />
20
20
</label>
21
21
<button id="connect" type="submit">connect</button>
22
22
</form>
23
23
24
24
<div id="handle-action" class="action">
25
-
<span id="handle"></span>
25
+
<span id="handle-view" class="handle"></span>
26
26
<button id="allow">Allow</button>
27
27
</div>
28
28
</div>
···
34
34
const promptEl = document.getElementById('prompt');
35
35
const loaderEl = document.getElementById('loader');
36
36
const infoEl = document.getElementById('user-info');
37
-
const handleEl = document.getElementById('handle');
37
+
const handleInputEl = document.getElementById('handle-input');
38
+
const handleViewEl = document.getElementById('handle-view');
38
39
const formEl = document.getElementById('form-action'); // for anon
39
-
const allowEl = document.getElementById('allow'); // for known-did
40
+
const allowEl = document.getElementById('handle-action'); // for known-did
40
41
const connectEl = document.getElementById('connect'); // for anon
41
42
42
43
function err(e, msg) {
···
46
47
throw new Error(e);
47
48
}
48
49
49
-
formEl && (formEl.onsubmit = e => {
50
+
// already-known user
51
+
({{{json did}}}) && (async () => {
52
+
53
+
const handle = await lookUp({{{json fetch_key}}});
54
+
console.log('got handle', handle);
55
+
56
+
loaderEl.classList.add('hidden');
57
+
handleViewEl.textContent = `@${handle}`;
58
+
allowEl.addEventListener('click', () => shareAllow(handle));
59
+
})();
60
+
61
+
// anon user
62
+
formEl.onsubmit = e => {
50
63
e.preventDefault();
51
64
loaderEl.classList.remove('hidden');
52
65
// TODO: include expected referer! (..this system is probably bad)
53
66
// maybe a random localstorage key that we specifically listen for?
54
67
const url = new URL('/auth', window.location);
55
-
url.searchParams.set('handle', handleEl.value);
68
+
url.searchParams.set('handle', handleInputEl.value);
56
69
window.open(url, '_blank');
57
-
});
70
+
};
58
71
59
72
window.addEventListener('storage', async e => {
60
73
// here's a fun minor vuln: we can't tell which flow triggers the storage event.
···
64
77
const fail = (e, msg) => {
65
78
loaderEl.classList.add('hidden');
66
79
formEl.classList.remove('hidden');
67
-
handleEl.focus();
68
-
handleEl.select();
80
+
handleInputEl.focus();
81
+
handleInputEl.select();
69
82
err(e, msg);
70
83
}
71
84
···
98
111
shareAllow(handle);
99
112
});
100
113
101
-
const lookUp = async fetch_key => {
114
+
async function lookUp(fetch_key) {
102
115
const user_info = new URL('/user-info', window.location);
103
116
user_info.searchParams.set('fetch-key', fetch_key);
104
117
let info;