Fossil SCM

Add a simple UI that allows any registered user (not "anonymous" or "nobody") to create access tokens.

drh 2025-08-16 16:48 trunk
Commit 2a3d3031243efce483ffef1934080bb425d7e4d8521bb120afcdc98d2d2d0d93
2 files changed +2 -1 +109 -1
+2 -1
--- src/login.c
+++ src/login.c
@@ -889,11 +889,12 @@
889889
if( !anonFlag ){
890890
@ <hr><p>
891891
@ Select your preferred <a href="%R/skins">site skin</a>.
892892
@ </p>
893893
@ <hr><p>
894
- @ Manage your <a href="%R/cookies">cookies</a>.</p>
894
+ @ Manage your <a href="%R/cookies">cookies</a> or your
895
+ @ <a href="%R/tokens">access tokens</a>.</p>
895896
}
896897
if( login_is_individual() ){
897898
if( g.perm.Password ){
898899
char *zRPW = fossil_random_password(12);
899900
@ <hr>
900901
--- src/login.c
+++ src/login.c
@@ -889,11 +889,12 @@
889 if( !anonFlag ){
890 @ <hr><p>
891 @ Select your preferred <a href="%R/skins">site skin</a>.
892 @ </p>
893 @ <hr><p>
894 @ Manage your <a href="%R/cookies">cookies</a>.</p>
 
895 }
896 if( login_is_individual() ){
897 if( g.perm.Password ){
898 char *zRPW = fossil_random_password(12);
899 @ <hr>
900
--- src/login.c
+++ src/login.c
@@ -889,11 +889,12 @@
889 if( !anonFlag ){
890 @ <hr><p>
891 @ Select your preferred <a href="%R/skins">site skin</a>.
892 @ </p>
893 @ <hr><p>
894 @ Manage your <a href="%R/cookies">cookies</a> or your
895 @ <a href="%R/tokens">access tokens</a>.</p>
896 }
897 if( login_is_individual() ){
898 if( g.perm.Password ){
899 char *zRPW = fossil_random_password(12);
900 @ <hr>
901
+109 -1
--- src/robot.c
+++ src/robot.c
@@ -170,11 +170,14 @@
170170
zGlob = db_get("robot-restrict",robot_restrict_default());
171171
if( zGlob==0 || zGlob[0]==0 ){ bKnownPass = 1; return 0; }
172172
if( !glob_multi_match(zGlob, zPage) ) return 0;
173173
zToken = P("token");
174174
if( zToken!=0
175
- && db_exists("SELECT 1 FROM config WHERE name='token-%q'", zToken)
175
+ && db_exists("SELECT 1 FROM config"
176
+ " WHERE name='token-%q'"
177
+ " AND json_valid(value,6)"
178
+ " AND value->>'user' IS NOT NULL", zToken)
176179
){
177180
bKnownPass = 1;
178181
return 0; /* There is a valid token= query parameter */
179182
}
180183
if( robot_proofofwork() ){
@@ -226,5 +229,110 @@
226229
}
227230
@ </p>
228231
@ <p><a href="%R/test-robotck/%h(zName)">Retry</a>
229232
style_finish_page();
230233
}
234
+
235
+/*
236
+** WEBPAGE: tokens
237
+**
238
+** Allow users to create, delete, and view their access token.
239
+**
240
+** The access token is a string TOKEN which if included in a query
241
+** parameter like "token=TOKEN" authenticates a request as coming
242
+** from an authorized agent. This can be used, for example, by
243
+** script to access content without running into problems with
244
+** robot defenses.
245
+*/
246
+void tokens_page(void){
247
+ char *zMyToken;
248
+
249
+ login_check_credentials();
250
+ style_set_current_feature("tokens");
251
+ style_header("Access Tokens");
252
+ if( g.zLogin==0 || fossil_strcmp(g.zLogin,"anonymous")==0 ){
253
+ @ User "%h(g.zLogin?g.zLogin:"anonymous")" is not allowed to
254
+ @ own or use access tokens.
255
+ style_finish_page();
256
+ return;
257
+ }
258
+ if( g.perm.Admin && P("del")!=0 ){
259
+ const char *zDel = P("del");
260
+ db_unprotect(PROTECT_CONFIG);
261
+ db_multi_exec(
262
+ "DELETE FROM config WHERE name='token-%q'",
263
+ zDel);
264
+ db_protect_pop();
265
+ }
266
+ zMyToken = db_text(0,
267
+ "SELECT substr(name,7) FROM config"
268
+ " WHERE name GLOB 'token-*'"
269
+ " AND json_valid(value,6)"
270
+ " AND value->>'user' = %Q",
271
+ g.zLogin
272
+ );
273
+ if( zMyToken==0 && P("new") ){
274
+ sqlite3_uint64 r;
275
+ sqlite3_randomness(sizeof(r),&r);
276
+ zMyToken = mprintf("%016llx", r);
277
+ db_unprotect(PROTECT_CONFIG);
278
+ db_multi_exec(
279
+ "INSERT INTO config(name,value,mtime)"
280
+ "VALUES('token-%q','{user:%!j}',now())",
281
+ zMyToken, g.zLogin
282
+ );
283
+ db_protect_pop();
284
+ }else if( zMyToken!=0 && P("selfdel")
285
+ && fossil_strcmp(zMyToken,P("selfdel"))==0 ){
286
+ db_unprotect(PROTECT_CONFIG);
287
+ db_multi_exec(
288
+ "DELETE FROM config WHERE name='token-%q'",
289
+ zMyToken);
290
+ db_protect_pop();
291
+ zMyToken = 0;
292
+ }
293
+ if( zMyToken==0 ){
294
+ @ <p>You do not currently have an access token.
295
+ @ <a href="%R/tokens?new=true">Create one</a>
296
+ }else{
297
+ @ <p>Your access token is "%h(zMyToken)".
298
+ @ <p>Use this token as the value of the token= query parameter
299
+ @ to bypass robot defenses on unauthenticated queries to this
300
+ @ server (%R). Do not misuse your token. Keep it confidential.
301
+ @ If you misuse your token, or if somebody else steals your token
302
+ @ and misuses, that can result in loss of access privileges to this
303
+ @ server.
304
+ @ <p><a href="%R/tokens?selfdel=%h(zMyToken)">Delete my token</a>
305
+ }
306
+ if( g.perm.Admin ){
307
+ int nTok = 0;
308
+ Stmt s;
309
+ db_prepare(&s,
310
+ "SELECT substr(name,7), value->>'user', datetime(mtime,'unixepoch')"
311
+ " FROM config"
312
+ " WHERE name GLOB 'token-*'"
313
+ " AND json_valid(value,6)"
314
+ );
315
+ while( db_step(&s)==SQLITE_ROW ){
316
+ if( nTok==0 ){
317
+ @ <hr>
318
+ @ <p>All tokens</p>
319
+ @ <table border="1" cellpadding="5" cellspacing="0">
320
+ @ <tr><th>User <th>Token <th>Date <th> &nbsp;</tr>
321
+ }
322
+ nTok++;
323
+ @ <tr><td>%h(db_column_text(&s,1))
324
+ @ <td>%h(db_column_text(&s,0))
325
+ @ <td>%h(db_column_text(&s,2))
326
+ @ <td><a href="%R/tokens?del=%h(db_column_text(&s,0))">delete</a>
327
+ @ </tr>
328
+ }
329
+ db_finalize(&s);
330
+ if( nTok==0 ){
331
+ @ <hr>
332
+ @ <p>There are access tokens defined for this repository.
333
+ }else{
334
+ @ </table>
335
+ }
336
+ }
337
+ style_finish_page();
338
+}
231339
--- src/robot.c
+++ src/robot.c
@@ -170,11 +170,14 @@
170 zGlob = db_get("robot-restrict",robot_restrict_default());
171 if( zGlob==0 || zGlob[0]==0 ){ bKnownPass = 1; return 0; }
172 if( !glob_multi_match(zGlob, zPage) ) return 0;
173 zToken = P("token");
174 if( zToken!=0
175 && db_exists("SELECT 1 FROM config WHERE name='token-%q'", zToken)
 
 
 
176 ){
177 bKnownPass = 1;
178 return 0; /* There is a valid token= query parameter */
179 }
180 if( robot_proofofwork() ){
@@ -226,5 +229,110 @@
226 }
227 @ </p>
228 @ <p><a href="%R/test-robotck/%h(zName)">Retry</a>
229 style_finish_page();
230 }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
--- src/robot.c
+++ src/robot.c
@@ -170,11 +170,14 @@
170 zGlob = db_get("robot-restrict",robot_restrict_default());
171 if( zGlob==0 || zGlob[0]==0 ){ bKnownPass = 1; return 0; }
172 if( !glob_multi_match(zGlob, zPage) ) return 0;
173 zToken = P("token");
174 if( zToken!=0
175 && db_exists("SELECT 1 FROM config"
176 " WHERE name='token-%q'"
177 " AND json_valid(value,6)"
178 " AND value->>'user' IS NOT NULL", zToken)
179 ){
180 bKnownPass = 1;
181 return 0; /* There is a valid token= query parameter */
182 }
183 if( robot_proofofwork() ){
@@ -226,5 +229,110 @@
229 }
230 @ </p>
231 @ <p><a href="%R/test-robotck/%h(zName)">Retry</a>
232 style_finish_page();
233 }
234
235 /*
236 ** WEBPAGE: tokens
237 **
238 ** Allow users to create, delete, and view their access token.
239 **
240 ** The access token is a string TOKEN which if included in a query
241 ** parameter like "token=TOKEN" authenticates a request as coming
242 ** from an authorized agent. This can be used, for example, by
243 ** script to access content without running into problems with
244 ** robot defenses.
245 */
246 void tokens_page(void){
247 char *zMyToken;
248
249 login_check_credentials();
250 style_set_current_feature("tokens");
251 style_header("Access Tokens");
252 if( g.zLogin==0 || fossil_strcmp(g.zLogin,"anonymous")==0 ){
253 @ User "%h(g.zLogin?g.zLogin:"anonymous")" is not allowed to
254 @ own or use access tokens.
255 style_finish_page();
256 return;
257 }
258 if( g.perm.Admin && P("del")!=0 ){
259 const char *zDel = P("del");
260 db_unprotect(PROTECT_CONFIG);
261 db_multi_exec(
262 "DELETE FROM config WHERE name='token-%q'",
263 zDel);
264 db_protect_pop();
265 }
266 zMyToken = db_text(0,
267 "SELECT substr(name,7) FROM config"
268 " WHERE name GLOB 'token-*'"
269 " AND json_valid(value,6)"
270 " AND value->>'user' = %Q",
271 g.zLogin
272 );
273 if( zMyToken==0 && P("new") ){
274 sqlite3_uint64 r;
275 sqlite3_randomness(sizeof(r),&r);
276 zMyToken = mprintf("%016llx", r);
277 db_unprotect(PROTECT_CONFIG);
278 db_multi_exec(
279 "INSERT INTO config(name,value,mtime)"
280 "VALUES('token-%q','{user:%!j}',now())",
281 zMyToken, g.zLogin
282 );
283 db_protect_pop();
284 }else if( zMyToken!=0 && P("selfdel")
285 && fossil_strcmp(zMyToken,P("selfdel"))==0 ){
286 db_unprotect(PROTECT_CONFIG);
287 db_multi_exec(
288 "DELETE FROM config WHERE name='token-%q'",
289 zMyToken);
290 db_protect_pop();
291 zMyToken = 0;
292 }
293 if( zMyToken==0 ){
294 @ <p>You do not currently have an access token.
295 @ <a href="%R/tokens?new=true">Create one</a>
296 }else{
297 @ <p>Your access token is "%h(zMyToken)".
298 @ <p>Use this token as the value of the token= query parameter
299 @ to bypass robot defenses on unauthenticated queries to this
300 @ server (%R). Do not misuse your token. Keep it confidential.
301 @ If you misuse your token, or if somebody else steals your token
302 @ and misuses, that can result in loss of access privileges to this
303 @ server.
304 @ <p><a href="%R/tokens?selfdel=%h(zMyToken)">Delete my token</a>
305 }
306 if( g.perm.Admin ){
307 int nTok = 0;
308 Stmt s;
309 db_prepare(&s,
310 "SELECT substr(name,7), value->>'user', datetime(mtime,'unixepoch')"
311 " FROM config"
312 " WHERE name GLOB 'token-*'"
313 " AND json_valid(value,6)"
314 );
315 while( db_step(&s)==SQLITE_ROW ){
316 if( nTok==0 ){
317 @ <hr>
318 @ <p>All tokens</p>
319 @ <table border="1" cellpadding="5" cellspacing="0">
320 @ <tr><th>User <th>Token <th>Date <th> &nbsp;</tr>
321 }
322 nTok++;
323 @ <tr><td>%h(db_column_text(&s,1))
324 @ <td>%h(db_column_text(&s,0))
325 @ <td>%h(db_column_text(&s,2))
326 @ <td><a href="%R/tokens?del=%h(db_column_text(&s,0))">delete</a>
327 @ </tr>
328 }
329 db_finalize(&s);
330 if( nTok==0 ){
331 @ <hr>
332 @ <p>There are access tokens defined for this repository.
333 }else{
334 @ </table>
335 }
336 }
337 style_finish_page();
338 }
339

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button