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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 951665db04cf6c2a4f8ef9b5c0555f4df91b84af08f63d21097d97cbd64d44b2
4
- data.tar.gz: 82457a522007f54ecd5fcc36fee8e79cf16a65996dd9b95d7a00fd0dcfbc2cac
3
+ metadata.gz: 43106e90c922f80d2bb342d051d68a6cb4efa045143c97b08c26bf776a952a6d
4
+ data.tar.gz: 1faee045587d15517d25ab68df5003c5a210db1d009b9b8363d6c27f0b53157d
5
5
  SHA512:
6
- metadata.gz: 031b1031a702aca3a7cee36ea8ba23bc4982e95f8381e59d07ecb652432f6bc8a40e9a386e4a254d640f18f0088d9e18fa1f6434d92c038844f4821cef40a703
7
- data.tar.gz: 5c5c36517efebb37a7a44d0531e0baadedb8600319682c39696a971771852019f2649386e15a3505d9ae32e86cf8c5818c64cf36943247220a7db6e0df24e994
6
+ metadata.gz: 8e696b00d7e79c968851c1f5a194fe352d8d9f7971ca37f56321705dd83ec8a71a3cae1836468df94b3877d5ac6a25cf31c991f30e10d0997ec0a6b06b2c9825
7
+ data.tar.gz: 92628aced329eb6e7c9567034873a276a15a64692e3796cf995cfbe1303132d6720fc69115332ab69371d07e5f4963377c80a5bad568797619c58ecc48fca04c
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Clacky
4
- VERSION = "0.9.37"
4
+ VERSION = "0.9.38"
5
5
  end
@@ -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
- let _authCheckPromise = null;
10
- let _authPassed = false;
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
- async function check() {
13
- if (_authCheckPromise) return _authCheckPromise;
14
- _authCheckPromise = _doCheck();
15
- return _authCheckPromise;
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
- async function _doCheck() {
19
- const key = _getStoredKey();
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
- if (r.ok) {
28
- _authPassed = true;
29
- return true;
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
- if (r.status === 401) {
33
- // Server requires auth — prompt for key.
34
- localStorage.removeItem('clacky_access_key');
35
- return await _promptAndRetry();
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
- // Other errors (5xx etc.) — let the app proceed.
39
- _authPassed = true;
40
- return true;
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
- // Network error — let the app proceed.
43
- _authPassed = true;
44
- return true;
48
+ return PROBE.NETWORK_ERR;
45
49
  }
46
50
  }
47
51
 
48
- async function _promptAndRetry() {
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 inputKey = (typeof Modal !== 'undefined' && Modal.prompt)
54
- ? await Modal.prompt(message)
55
- : prompt(message);
58
+ const el = document.getElementById('prompt-modal-input');
59
+ if (el) el.type = 'password';
56
60
 
57
- if (!inputKey || !inputKey.trim()) {
58
- _authPassed = false;
59
- return false;
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
- const trimmed = inputKey.trim();
63
- localStorage.setItem('clacky_access_key', trimmed);
71
+ // ── Core flow ──────────────────────────────────────────────────────────
72
+ async function _doCheck() {
73
+ const existing = _getStoredKey();
74
+ if (existing) Cookie.set(existing); // seed cookie before probe
64
75
 
65
- // Validate the newly entered key without reloading.
66
- try {
67
- const r = await fetch('/api/sessions?limit=1', {
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 _getStoredKey() {
85
- return localStorage.getItem('clacky_access_key') ||
86
- new URLSearchParams(location.search).get('access_key') || null;
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
- function getHeaders() {
90
- const key = _getStoredKey();
91
- return key ? { 'Authorization': `Bearer ${key}` } : {};
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
- getHeaders,
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
- reset() { _authCheckPromise = null; },
99
- get passed() { return _authPassed; }
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
  })();
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: openclacky
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.37
4
+ version: 0.9.38
5
5
  platform: ruby
6
6
  authors:
7
7
  - windy