openclacky 0.9.37 → 0.9.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/clacky/version.rb +1 -1
- data/lib/clacky/web/auth.js +118 -69
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 43106e90c922f80d2bb342d051d68a6cb4efa045143c97b08c26bf776a952a6d
|
|
4
|
+
data.tar.gz: 1faee045587d15517d25ab68df5003c5a210db1d009b9b8363d6c27f0b53157d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8e696b00d7e79c968851c1f5a194fe352d8d9f7971ca37f56321705dd83ec8a71a3cae1836468df94b3877d5ac6a25cf31c991f30e10d0997ec0a6b06b2c9825
|
|
7
|
+
data.tar.gz: 92628aced329eb6e7c9567034873a276a15a64692e3796cf995cfbe1303132d6720fc69115332ab69371d07e5f4963377c80a5bad568797619c58ecc48fca04c
|
data/lib/clacky/version.rb
CHANGED
data/lib/clacky/web/auth.js
CHANGED
|
@@ -1,101 +1,150 @@
|
|
|
1
|
-
// ── auth.js — Access key authentication guard ──────────────────────────────
|
|
2
|
-
//
|
|
3
|
-
// Responsibilities:
|
|
4
|
-
// - Prompt user for access key when server requires one
|
|
5
|
-
// - Cache key in localStorage; pass via Authorization header
|
|
6
|
-
// - Block app boot until auth passes
|
|
7
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
8
1
|
const Auth = (() => {
|
|
9
|
-
|
|
10
|
-
|
|
2
|
+
// ── Constants ──────────────────────────────────────────────────────────
|
|
3
|
+
const COOKIE_NAME = 'clacky_access_key';
|
|
4
|
+
const STORAGE_KEY = 'clacky_access_key';
|
|
5
|
+
const PROBE_ENDPOINT = '/api/sessions?limit=1';
|
|
6
|
+
const MAX_PROMPT_TRIES = 3;
|
|
11
7
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
const PROBE = Object.freeze({
|
|
9
|
+
OK: 'ok',
|
|
10
|
+
UNAUTHORIZED: 'unauthorized',
|
|
11
|
+
SERVER_ERR: 'server_error',
|
|
12
|
+
NETWORK_ERR: 'network_error',
|
|
13
|
+
});
|
|
17
14
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
// Always probe first — server may not require auth at all (e.g. localhost).
|
|
22
|
-
try {
|
|
23
|
-
const r = await fetch('/api/sessions?limit=1', {
|
|
24
|
-
headers: key ? { 'Authorization': `Bearer ${key}` } : {}
|
|
25
|
-
});
|
|
15
|
+
// ── Module state ───────────────────────────────────────────────────────
|
|
16
|
+
let _authCheckPromise = null;
|
|
17
|
+
let _authPassed = false;
|
|
26
18
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
19
|
+
// ── Storage helpers ────────────────────────────────────────────────────
|
|
20
|
+
const Cookie = {
|
|
21
|
+
set(key) {
|
|
22
|
+
const secure = location.protocol === 'https:' ? '; Secure' : '';
|
|
23
|
+
document.cookie =
|
|
24
|
+
`${COOKIE_NAME}=${encodeURIComponent(key)}; path=/; SameSite=Strict${secure}`;
|
|
25
|
+
},
|
|
26
|
+
clear() {
|
|
27
|
+
document.cookie =
|
|
28
|
+
`${COOKIE_NAME}=; path=/; max-age=0; SameSite=Strict`;
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
32
|
+
function _getStoredKey() {
|
|
33
|
+
return (
|
|
34
|
+
localStorage.getItem(STORAGE_KEY) ||
|
|
35
|
+
new URLSearchParams(location.search).get('access_key') ||
|
|
36
|
+
null
|
|
37
|
+
);
|
|
38
|
+
}
|
|
37
39
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
40
|
+
// ── Auth probe ─────────────────────────────────────────────────────────
|
|
41
|
+
async function _probe() {
|
|
42
|
+
try {
|
|
43
|
+
const r = await fetch(PROBE_ENDPOINT);
|
|
44
|
+
if (r.ok) return PROBE.OK;
|
|
45
|
+
if (r.status === 401) return PROBE.UNAUTHORIZED;
|
|
46
|
+
return PROBE.SERVER_ERR;
|
|
41
47
|
} catch {
|
|
42
|
-
|
|
43
|
-
_authPassed = true;
|
|
44
|
-
return true;
|
|
48
|
+
return PROBE.NETWORK_ERR;
|
|
45
49
|
}
|
|
46
50
|
}
|
|
47
51
|
|
|
48
|
-
|
|
52
|
+
// ── Prompt helper ──────────────────────────────────────────────────────
|
|
53
|
+
async function _askUserForKey() {
|
|
49
54
|
const message = (typeof I18n !== 'undefined')
|
|
50
55
|
? I18n.t('auth.accessKeyRequired')
|
|
51
56
|
: 'Access key required:';
|
|
52
57
|
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
: prompt(message);
|
|
58
|
+
const el = document.getElementById('prompt-modal-input');
|
|
59
|
+
if (el) el.type = 'password';
|
|
56
60
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
try {
|
|
62
|
+
const input = (typeof Modal !== 'undefined' && Modal.prompt)
|
|
63
|
+
? await Modal.prompt(message)
|
|
64
|
+
: prompt(message);
|
|
65
|
+
return input?.trim() || null;
|
|
66
|
+
} finally {
|
|
67
|
+
if (el) el.type = 'text';
|
|
60
68
|
}
|
|
69
|
+
}
|
|
61
70
|
|
|
62
|
-
|
|
63
|
-
|
|
71
|
+
// ── Core flow ──────────────────────────────────────────────────────────
|
|
72
|
+
async function _doCheck() {
|
|
73
|
+
const existing = _getStoredKey();
|
|
74
|
+
if (existing) Cookie.set(existing); // seed cookie before probe
|
|
64
75
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
headers: { 'Authorization': `Bearer ${trimmed}` }
|
|
69
|
-
});
|
|
70
|
-
if (r.ok) {
|
|
71
|
-
_authPassed = true;
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
// Still wrong — clear storage and restart the auth flow.
|
|
75
|
-
localStorage.removeItem('clacky_access_key');
|
|
76
|
-
_authCheckPromise = null;
|
|
77
|
-
return check();
|
|
78
|
-
} catch {
|
|
76
|
+
const result = await _probe();
|
|
77
|
+
|
|
78
|
+
if (result === PROBE.OK) {
|
|
79
79
|
_authPassed = true;
|
|
80
80
|
return true;
|
|
81
81
|
}
|
|
82
|
+
|
|
83
|
+
if (result === PROBE.UNAUTHORIZED) {
|
|
84
|
+
Cookie.clear();
|
|
85
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
86
|
+
return _promptAndRetry();
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Server/network error — let app proceed
|
|
90
|
+
_authPassed = true;
|
|
91
|
+
return true;
|
|
82
92
|
}
|
|
83
93
|
|
|
84
|
-
function
|
|
85
|
-
|
|
86
|
-
|
|
94
|
+
async function _promptAndRetry() {
|
|
95
|
+
for (let attempt = 1; attempt <= MAX_PROMPT_TRIES; attempt++) {
|
|
96
|
+
const key = await _askUserForKey();
|
|
97
|
+
if (!key) {
|
|
98
|
+
_authPassed = false;
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
Cookie.set(key);
|
|
103
|
+
const result = await _probe();
|
|
104
|
+
|
|
105
|
+
if (result === PROBE.OK) {
|
|
106
|
+
localStorage.setItem(STORAGE_KEY, key); // persist only after success
|
|
107
|
+
_authPassed = true;
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (result !== PROBE.UNAUTHORIZED) {
|
|
112
|
+
_authPassed = true; // transient — proceed
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
Cookie.clear(); // wrong key → try again
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
_authPassed = false;
|
|
120
|
+
return false;
|
|
87
121
|
}
|
|
88
122
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
123
|
+
// ── Public API (compatible with the original ws.js/app.js usage) ───────
|
|
124
|
+
function check() {
|
|
125
|
+
if (!_authCheckPromise) _authCheckPromise = _doCheck();
|
|
126
|
+
return _authCheckPromise;
|
|
92
127
|
}
|
|
93
128
|
|
|
94
129
|
return {
|
|
95
130
|
check,
|
|
96
|
-
|
|
131
|
+
|
|
132
|
+
// Returns an Authorization header object, or {} if no key present.
|
|
133
|
+
getHeaders() {
|
|
134
|
+
const k = _getStoredKey();
|
|
135
|
+
return k ? { Authorization: `Bearer ${k}` } : {};
|
|
136
|
+
},
|
|
137
|
+
|
|
138
|
+
// Returns the raw key (or null). Used by ws.js for WebSocket URLs.
|
|
97
139
|
getKey: _getStoredKey,
|
|
98
|
-
|
|
99
|
-
|
|
140
|
+
|
|
141
|
+
// Clears auth state so check() will re-probe on next call.
|
|
142
|
+
reset() {
|
|
143
|
+
_authCheckPromise = null;
|
|
144
|
+
_authPassed = false;
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
// Read-only getter: `Auth.passed` (not a function call).
|
|
148
|
+
get passed() { return _authPassed; },
|
|
100
149
|
};
|
|
101
150
|
})();
|