Fossil SCM

fossil-scm / src / robot.c
Blame History Raw 709 lines
1
/*
2
** Copyright (c) 2025 D. Richard Hipp
3
**
4
** This program is free software; you can redistribute it and/or
5
** modify it under the terms of the Simplified BSD License (also
6
** known as the "2-Clause License" or "FreeBSD License".)
7
8
** This program is distributed in the hope that it will be useful,
9
** but without any warranty; without even the implied warranty of
10
** merchantability or fitness for a particular purpose.
11
**
12
** Author contact information:
13
** [email protected]
14
** http://www.hwaci.com/drh/
15
**
16
*******************************************************************************
17
**
18
** This file contains code that attempts to prevent robots and
19
** especially bot-nets from consume excess CPU and bandwidth when
20
** Fossil is run as a service.
21
*/
22
#include "config.h"
23
#include "robot.h"
24
#include <assert.h>
25
#include <time.h>
26
27
/*
28
** The name of the cookie used to demonstrate that the client has been
29
** tested and is believed to be operated by a human, not by a robot.
30
*/
31
#if INTERFACE
32
#define ROBOT_COOKIE "fossil-client-ok"
33
#endif
34
35
/*
36
** Values computed only once and then cached.
37
*/
38
static struct RobotCache {
39
unsigned int h1, h2; /* Proof-of-work hash values */
40
unsigned int resultCache; /* 0: unknown. 1: human 2: might-be-robot */
41
} robot = { 0, 0, 0 };
42
43
/*
44
** Allowed values for robot.resultCache.
45
**
46
** The names are slightly misleading. KNOWN_NOT_ROBOT might be set even
47
** if the client is a robot, but only if the robot is an approved robot.
48
** A better name might be "KNOWN_NOT_UNAUTHORIZED_ROBOT", but that is too
49
** long of a name.
50
*/
51
#define KNOWN_NOT_ROBOT 1 /* Approved to consume CPU and bandwidth */
52
#define MIGHT_BE_ROBOT 2 /* Might be an unapproved robot */
53
54
/*
55
** Compute two hashes, robot.h1 and robot.h2, that are used as
56
** part of determining whether or not the HTTP client is a robot.
57
** These hashes are based on current time, client IP address,
58
** and User-Agent. robot.h1 is for the current time slot and
59
** robot.h2 is the previous.
60
**
61
** The hashes are integer values between 100,000,000 and 999,999,999
62
** inclusive.
63
*/
64
static void robot_pow_hash(void){
65
const char *az[2], *z;
66
sqlite3_int64 tm;
67
unsigned int h1, h2, k;
68
69
if( robot.h1 ) return; /* Already computed */
70
71
/* Construct a proof-of-work value based on the IP address of the
72
** sender and the sender's user-agent string. The current time also
73
** affects the pow value, so actually compute two values, one for the
74
** current 900-second interval and one for the previous. Either can
75
** match. The pow-value is an integer between 100,000,000 and
76
** 999,999,999.
77
*/
78
az[0] = P("REMOTE_ADDR");
79
az[1] = P("HTTP_USER_AGENT");
80
tm = time(0);
81
h1 = (unsigned)(tm/900)&0xffffffff;
82
h2 = h1 - 1;
83
for(k=0; k<2; k++){
84
z = az[k];
85
if( z==0 ) continue;
86
while( *z ){
87
h1 = (h1 + *(unsigned char*)z)*0x9e3779b1;
88
h2 = (h2 + *(unsigned char*)z)*0x9e3779b1;
89
z++;
90
}
91
}
92
robot.h1 = (h1 % 900000000) + 100000000;
93
robot.h2 = (h2 % 900000000) + 100000000;
94
}
95
96
/*
97
** Return true if the HTTP client has not demonstrated that it is
98
** human interactive. Return false is the HTTP client might be
99
** a non-interactive robot.
100
**
101
** For this routine, any of the following is considered proof that
102
** the HTTP client is not a robot:
103
**
104
** 1. There is a valid login, including "anonymous". User "nobody"
105
** is not a valid login, but every other user is.
106
**
107
** 2. There exists a ROBOT_COOKIE with the correct proof-of-work
108
** value.
109
**
110
** 3. There exists a proof=VALUE query parameter where VALUE is
111
** a correct proof-of-work value.
112
**
113
** 4. There exists a valid token=VALUE query parameter.
114
**
115
** After being run once, this routine caches its findings and
116
** returns very quickly on subsequent invocations.
117
*/
118
int client_might_be_a_robot(void){
119
const char *z;
120
121
/* Only do this computation once, then cache the results for future
122
** use */
123
if( robot.resultCache ){
124
return robot.resultCache==MIGHT_BE_ROBOT;
125
}
126
127
/* Condition 1: Is there a valid login?
128
*/
129
if( g.userUid==0 ){
130
login_check_credentials();
131
}
132
if( g.zLogin!=0 ){
133
robot.resultCache = KNOWN_NOT_ROBOT;
134
return 0;
135
}
136
137
/* Condition 2: If there is already a proof-of-work cookie
138
** with a correct value, then the user agent has been authenticated.
139
*/
140
z = P(ROBOT_COOKIE);
141
if( z ){
142
unsigned h = atoi(z);
143
robot_pow_hash();
144
if( (h==robot.h1 || h==robot.h2) && !cgi_is_qp(ROBOT_COOKIE) ){
145
robot.resultCache = KNOWN_NOT_ROBOT;
146
return 0;
147
}
148
}
149
150
/* Condition 3: There is a "proof=VALUE" query parameter with a valid
151
** VALUE attached. If this is the case, also set the robot cookie
152
** so that future requests will hit condition 2 above.
153
*/
154
z = P("proof");
155
if( z ){
156
unsigned h = atoi(z);
157
robot_pow_hash();
158
if( h==robot.h1 || h==robot.h2 ){
159
cgi_set_cookie(ROBOT_COOKIE,z,"/",900);
160
robot.resultCache = KNOWN_NOT_ROBOT;
161
return 0;
162
}
163
cgi_tag_query_parameter("proof");
164
}
165
166
/* Condition 4: If there is a "token=VALUE" query parameter with a
167
** valid VALUE argument, then assume that the request is coming from
168
** either an interactive human session, or an authorized robot that we
169
** want to treat as human. Allow it through and also set the robot cookie.
170
*/
171
z = P("token");
172
if( z!=0 ){
173
if( db_exists("SELECT 1 FROM config"
174
" WHERE name='token-%q'"
175
" AND json_valid(value,6)"
176
" AND value->>'user' IS NOT NULL", z)
177
){
178
char *zVal;
179
robot_pow_hash();
180
zVal = mprintf("%u", robot.h1);
181
cgi_set_cookie(ROBOT_COOKIE,zVal,"/",900);
182
fossil_free(zVal);
183
robot.resultCache = KNOWN_NOT_ROBOT;
184
return 0; /* There is a valid token= query parameter */
185
}
186
cgi_tag_query_parameter("token");
187
}
188
189
/* We have no proof that the request is coming from an interactive
190
** human session, so assume the request comes from a robot.
191
*/
192
robot.resultCache = MIGHT_BE_ROBOT;
193
return 1;
194
}
195
196
/*
197
** Rewrite the current page with content that attempts
198
** to prove that the client is not a robot.
199
*/
200
static void ask_for_proof_that_client_is_not_robot(void){
201
unsigned p1, p2, p3, p4, p5, k2, k3;
202
int k;
203
204
/* Ask the client to present proof-of-work */
205
cgi_reset_content();
206
cgi_set_content_type("text/html");
207
style_header("Browser Verification");
208
@ <h1 id="x1">Checking to see if you are a robot<span id="x2"></span></h1>
209
@ <form method="GET" id="x6"><p>
210
@ <span id="x3" style="visibility:hidden;">\
211
@ Press <input type="submit" id="x5" value="Ok" focus> to continue</span>
212
@ <span id="x7" style="visibility:hidden;">You appear to be a robot.</span>\
213
@ </p>
214
if( g.zExtra && g.zExtra[0] ) cgi_tag_query_parameter("name");
215
cgi_query_parameters_to_hidden();
216
@ <input id="x4" type="hidden" name="proof" value="0">
217
@ </form>
218
@ <script nonce='%s(style_nonce())'>
219
@ function aaa(x){return document.getElementById(x);}\
220
@ function bbb(h,a){\
221
@ aaa("x4").value=h;\
222
@ if((a%%75)==0){\
223
@ aaa("x2").textContent=aaa("x2").textContent+".";\
224
@ }var z;\
225
@ if(a>0){\
226
@ setTimeout(bbb,1,h+a,a-1);\
227
@ }else if((z=window.getComputedStyle(document.body).zIndex)==='0'||z===0){\
228
@ aaa("x3").style.visibility="visible";\
229
@ aaa("x2").textContent="";\
230
@ aaa("x1").textContent="All clear";\
231
@ aaa("x6").onsubmit=function(){aaa("x3").style.visibility="hidden";};\
232
@ aaa("x5").focus();\
233
@ }else{\
234
@ aaa("x7").style.visibility="visible";\
235
@ aaa("x2").textContent="";\
236
@ aaa("x3").style.display="none";\
237
@ aaa("x1").textContent="Access Denied";\
238
@ }\
239
@ }\
240
robot_pow_hash();
241
k = 400 + robot.h2%299;
242
k2 = (robot.h2/299)%99 + 973;
243
k3 = (robot.h2/(299*99))%99 + 811;
244
p1 = (k*k + k)/2;
245
p2 = robot.h1-p1;
246
p3 = p2%k2;
247
p4 = (p2/k2)%k3;
248
p5 = p2/(k2*k3);
249
@ function ccc(a,b,c){return (a*%u(k3)+b)*%u(k2)+c;}\
250
@ window.addEventListener('load',function(){\
251
@ bbb(ccc(%u(p5),%u(p4),%u(p3)),%u(k));},false);
252
/* Prevent successfully completed robot checks from reappearing and force
253
** incomplete checks to start over when navigating back and forward. More
254
** information: <https://stackoverflow.com/a/43043658>. */
255
@ window.addEventListener('pageshow',function(e){if(e.persisted)\
256
@ window.location.reload();});
257
@ </script>
258
style_finish_page();
259
}
260
261
/*
262
** SETTING: robot-restrict width=40 block-text
263
** The VALUE of this setting is a list of GLOB patterns that match
264
** pages for which complex HTTP requests from unauthenticated clients
265
** should be disallowed. "Unauthenticated" means the user is "nobody".
266
** The recommended value for this setting is:
267
**
268
** timelineX,diff,annotate,fileage,file,finfo,reports,tree,hexdump,download
269
**
270
** Usually the tag should exactly match the page name. The "diff" tag
271
** covers all diffing pages such as /vdiff, /fdiff, and /vpatch. The
272
** "annotate" tag also covers /blame and /praise. "zip" also covers
273
** /tarball and /sqlar. If a tag has an "X" character appended then it
274
** only applies if query parameters are such that the page is particularly
275
** difficult to compute. Useful "X" tags include "timelineX" and "zipX".
276
** The "ext" tag matches all extension, but a tag of the form "ext/PATH"
277
** only matches the extension at PATH.
278
**
279
** See the [[robot-zip-leaf]] and [[robot-zip-tag]] settings
280
** for additional controls associated with the "zipX" restriction.
281
**
282
** Change this setting "off" to disable all robot restrictions.
283
*/
284
/*
285
** SETTING: robot-exception width=40 block-text
286
**
287
** The value of this setting should be a regular expression.
288
** If it matches the REQUEST_URI without the SCRIPT_NAME prefix
289
** matches this regular expression, then the request is an exception
290
** to anti-robot defenses and should be allowed through. For
291
** example, to allow robots to download tarballs or ZIP archives
292
** for named versions and releases, you could use an expression like
293
** this:
294
**
295
** ^/tarball/(version-[0-9.]+|release)/
296
**
297
** This setting can hold multiple regular expressions, one
298
** regular expression per line. The input URL is exempted from
299
** anti-robot defenses if any of the multiple regular expressions
300
** matches.
301
*/
302
/*
303
** SETTING: robot-zip-leaf boolean
304
**
305
** If this setting is true, the robots are allowed to download tarballs,
306
** ZIP-archives, and SQL-archives even though "zipX" is found in
307
** the [[robot-restrict]] setting as long as the specific check-in being
308
** downloaded is a leaf check-in.
309
*/
310
/*
311
** SETTING: robot-zip-tag width=40 block-text
312
**
313
** If this setting is a list of GLOB patterns matching tags,
314
** then robots are allowed to download tarballs, ZIP-archives, and
315
** SQL-archives even though "zipX" appears in [[robot-restrict]], as long as
316
** the specific check-in being downloaded has a tags that matches
317
** the GLOB list of this setting. Recommended value:
318
** "release,robot-access".
319
*/
320
321
/*
322
** Return the default restriction GLOB
323
*/
324
const char *robot_restrict_default(void){
325
/* NOTE: The default value is also mentioned in the online help screen of
326
** the "robot-restrict" setting, and in the www/antibot.wiki document. */
327
return "timelineX,diff,annotate,fileage,file,finfo,reports,"
328
"tree,hexdump,download";
329
}
330
331
/*
332
** Return true if zTag matches one of the tags in the robot-restrict
333
** setting.
334
**
335
** A zTag of "*" matches anything.
336
*/
337
static int robot_restrict_has_tag(const char *zTag){
338
static const char *zGlob = 0;
339
if( zGlob==0 ){
340
zGlob = db_get("robot-restrict",robot_restrict_default());
341
if( zGlob==0 ) zGlob = "";
342
}
343
if( zGlob[0]==0 || fossil_strcmp(zGlob, "off")==0 ){
344
return 0;
345
}
346
if( zTag==0 || (zTag[0]=='*' && zTag[1]==0) ){
347
return 1;
348
}
349
return glob_multi_match(zGlob,zTag);
350
}
351
352
/*
353
** Check the request URI to see if it matches one of the URI
354
** exceptions listed in the robot-exception setting. Return true
355
** if it does. Return false if it does not.
356
**
357
** For the purposes of this routine, the "request URI" means
358
** the REQUEST_URI value with the SCRIPT_NAME prefix removed and
359
** with QUERY_STRING appended with a "?" separator if QUERY_STRING
360
** is not empty.
361
**
362
** If the robot-exception setting does not exist or is an empty
363
** string, then return false.
364
*/
365
int robot_exception(void){
366
const char *zRE = db_get("robot-exception",0);
367
const char *zQS; /* QUERY_STRING */
368
const char *zURI; /* REQUEST_URI */
369
const char *zSN; /* SCRIPT_NAME */
370
const char *zNL; /* Next newline character */
371
char *zRequest; /* REQUEST_URL w/o SCRIPT_NAME prefix + QUERY_STRING */
372
int nRequest; /* Length of zRequest in bytes */
373
size_t nURI, nSN; /* Length of zURI and zSN */
374
int bMatch = 0; /* True if there is a match */
375
376
if( zRE==0 ) return 0;
377
if( zRE[0]==0 ) return 0;
378
zURI = PD("REQUEST_URI","");
379
nURI = strlen(zURI);
380
zSN = PD("SCRIPT_NAME","");
381
nSN = strlen(zSN);
382
if( nSN<=nURI ) zURI += nSN;
383
zQS = P("QUERY_STRING");
384
if( zQS && zQS[0] ){
385
zRequest = mprintf("%s?%s", zURI, zQS);
386
}else{
387
zRequest = fossil_strdup(zURI);
388
}
389
nRequest = (int)strlen(zRequest);
390
while( zRE[0] && bMatch==0 ){
391
char *z;
392
const char *zErr;
393
size_t n;
394
ReCompiled *pRe;
395
zNL = strchr(zRE,'\n');
396
if( zNL ){
397
n = (size_t)(zNL - zRE)+1;
398
while( zNL>zRE && fossil_isspace(zNL[0]) ) zNL--;
399
if( zNL==zRE ){
400
zRE += n;
401
continue;
402
}
403
}else{
404
n = strlen(zRE);
405
}
406
z = mprintf("%.*s", (int)(zNL - zRE)+1, zRE);
407
zRE += n;
408
zErr = fossil_re_compile(&pRe, z, 0);
409
if( zErr ){
410
fossil_warning("robot-exception error \"%s\" in expression \"%s\"\n",
411
zErr, z);
412
fossil_free(z);
413
continue;
414
}
415
fossil_free(z);
416
bMatch = re_match(pRe, (const unsigned char*)zRequest, nRequest);
417
re_free(pRe);
418
}
419
fossil_free(zRequest);
420
return bMatch;
421
}
422
423
/*
424
** Return true if one or more of the conditions below are true.
425
** Return false if all of the following are false:
426
**
427
** * The zTag is on the robot-restrict list
428
**
429
** * The client that submitted the HTTP request might be
430
** a robot
431
**
432
** * The Request URI does not match any of the exceptions
433
** in the robot-exception setting.
434
**
435
** In other words, return true if a call to robot_restrict() would
436
** return true and false if a call to robot_restrict() would return
437
** false.
438
**
439
** The difference between this routine an robot_restrict() is that
440
** this routine does not generate a proof-of-work captcha. This
441
** routine does not change the HTTP reply in any way. It simply
442
** returns true or false.
443
*/
444
int robot_would_be_restricted(const char *zTag){
445
if( robot.resultCache==KNOWN_NOT_ROBOT ) return 0;
446
if( !robot_restrict_has_tag(zTag) ) return 0;
447
if( !client_might_be_a_robot() ) return 0;
448
if( robot_exception() ){
449
robot.resultCache = KNOWN_NOT_ROBOT;
450
return 0;
451
}
452
return 1;
453
}
454
455
/*
456
** Check to see if the page named in the argument is on the
457
** robot-restrict list. If it is on the list and if the user
458
** is might be a robot, then bring up a captcha to test to make
459
** sure that client is not a robot.
460
**
461
** This routine returns true if a captcha was rendered and if subsequent
462
** page generation should be aborted. It returns false if the page
463
** should not be restricted and should be rendered normally.
464
*/
465
int robot_restrict(const char *zTag){
466
if( robot_would_be_restricted(zTag) ){
467
/* Generate the proof-of-work captcha */
468
ask_for_proof_that_client_is_not_robot();
469
return 1;
470
}else{
471
return 0;
472
}
473
}
474
475
/*
476
** Check to see if a robot is allowed to download a tarball, ZIP archive,
477
** or SQL Archive for a particular check-in identified by the "rid"
478
** argument. Return true to block the download. Return false to
479
** continue. Prior to returning true, a captcha is presented to the user.
480
** No output is generated when returning false.
481
**
482
** The rules:
483
**
484
** (1) If "zipX" is missing from the robot-restrict setting, then robots
485
** are allowed to download any archive. None of the remaining rules
486
** below are consulted unless "zipX" is on the robot-restrict setting.
487
**
488
** (2) If the robot-zip-leaf setting is true, then robots are allowed
489
** to download archives for any leaf check-in. This allows URL like
490
** /tarball/trunk/archive.tar.gz to work since branch labels like "trunk"
491
** always resolve to a leaf.
492
**
493
** (3) If the robot-zip-tag setting is a comma-separated tags, then any
494
** check-in that contains one of the tags on that list is allowed to
495
** be downloaded. This allows check-ins with tags like "release" or
496
** "robot-access" to be downloaded by robots.
497
*/
498
int robot_restrict_zip(int rid){
499
const char *zTag;
500
if( !robot_restrict_has_tag("zipX") || !client_might_be_a_robot() ){
501
return 0; /* Rule (1) */
502
}
503
504
if( db_get_boolean("robot-zip-leaf",0) && is_a_leaf(rid) ){
505
return 0; /* Rule (2) */
506
}
507
508
zTag = db_get("robot-zip-tag",0);
509
if( zTag && zTag[0] && fossil_strcmp(zTag,"off")!=0 ){
510
int ok = 0;
511
Stmt q;
512
db_prepare(&q,
513
"SELECT substr(tagname,5) FROM tagxref, tag"
514
" WHERE tagxref.rid=%d"
515
" AND tag.tagid=tagxref.tagid"
516
" AND tagxref.tagtype=1"
517
" AND tag.tagname GLOB 'sym-*'",
518
rid
519
);
520
while( !ok && db_step(&q)==SQLITE_ROW ){
521
if( glob_multi_match(zTag, db_column_text(&q,0)) ) ok = 1;
522
}
523
db_finalize(&q);
524
if( ok ) return 0; /* Rule (3) */
525
}
526
527
/* Generate the proof-of-work captcha */
528
ask_for_proof_that_client_is_not_robot();
529
return 1;
530
}
531
532
/*
533
** WEBPAGE: test-robotck
534
**
535
** Run the robot_restrict() function using the value of the "name="
536
** query parameter as an argument. Used for testing the robot_restrict()
537
** logic.
538
**
539
** Whenever this page is successfully rendered (when it doesn't go to
540
** the captcha) it deletes the proof-of-work cookie. So reloading the
541
** page will reset the cookie and restart the verification.
542
**
543
** If the zip=CHECKIN query parameter is provided, then also invoke
544
** robot_restrict_archive() on the RID of CHECKIN.
545
*/
546
void robot_restrict_test_page(void){
547
const char *zName = P("name");
548
const char *zZip = P("zip");
549
const char *zP1 = P("proof");
550
const char *zP2 = P(ROBOT_COOKIE);
551
const char *z;
552
int rid = 0;
553
if( zName==0 || zName[0]==0 ) zName = g.zPath;
554
login_check_credentials();
555
if( g.zLogin==0 ){ login_needed(1); return; }
556
g.zLogin = 0;
557
if( robot_restrict(zName) ) return;
558
if( zZip && zZip[0] ){
559
rid = symbolic_name_to_rid(zZip, "ci");
560
if( rid && robot_restrict_zip(rid) ) return;
561
}
562
style_set_current_feature("test");
563
style_header("robot_restrict() test");
564
@ <h1>Captcha passed</h1>
565
@
566
@ <p>
567
if( zP1 && zP1[0] ){
568
@ proof=%h(zP1)<br>
569
}
570
if( zP2 && zP2[0] ){
571
@ %h(ROBOT_COOKIE)=%h(zP2)<br>
572
cgi_set_cookie(ROBOT_COOKIE,"",0,-1);
573
}
574
if( zZip && zZip[0] ){
575
@ zip=%h(zZip)<br>
576
@ rid=%d(rid)<br>
577
}
578
if( g.perm.Admin ){
579
z = db_get("robot-restrict",robot_restrict_default());
580
if( z && z[0] ){
581
@ robot-restrict=%h(z)</br>
582
}
583
@ robot.h1=%u(robot.h1)<br>
584
@ robot.h2=%u(robot.h2)<br>
585
switch( robot.resultCache ){
586
case MIGHT_BE_ROBOT: {
587
@ robot.resultCache=MIGHT_BE_ROBOT<br>
588
break;
589
}
590
case KNOWN_NOT_ROBOT: {
591
@ robot.resultCache=KNOWN_NOT_ROBOT<br>
592
break;
593
}
594
default: {
595
@ robot.resultCache=OTHER (%d(robot.resultCache))<br>
596
break;
597
}
598
}
599
}
600
@ </p>
601
@ <p><a href="%R/test-robotck/%h(zName)">Retry</a>
602
style_finish_page();
603
}
604
605
/*
606
** WEBPAGE: tokens
607
**
608
** Allow users to create, delete, and view their access token.
609
**
610
** The access token is a string TOKEN which if included in a query
611
** parameter like "token=TOKEN" authenticates a request as coming
612
** from an authorized agent. This can be used, for example, by
613
** script to access content without running into problems with
614
** robot defenses.
615
*/
616
void tokens_page(void){
617
char *zMyToken;
618
619
login_check_credentials();
620
style_set_current_feature("tokens");
621
style_header("Access Tokens");
622
if( g.zLogin==0 || fossil_strcmp(g.zLogin,"anonymous")==0 ){
623
@ User "%h(g.zLogin?g.zLogin:"anonymous")" is not allowed to
624
@ own or use access tokens.
625
style_finish_page();
626
return;
627
}
628
if( g.perm.Admin && P("del")!=0 ){
629
const char *zDel = P("del");
630
db_unprotect(PROTECT_CONFIG);
631
db_multi_exec(
632
"DELETE FROM config WHERE name='token-%q'",
633
zDel);
634
db_protect_pop();
635
}
636
zMyToken = db_text(0,
637
"SELECT substr(name,7) FROM config"
638
" WHERE name GLOB 'token-*'"
639
" AND json_valid(value,6)"
640
" AND value->>'user' = %Q",
641
g.zLogin
642
);
643
if( zMyToken==0 && P("new") ){
644
sqlite3_uint64 r;
645
sqlite3_randomness(sizeof(r),&r);
646
zMyToken = mprintf("%016llx", r);
647
db_unprotect(PROTECT_CONFIG);
648
db_multi_exec(
649
"INSERT INTO config(name,value,mtime)"
650
"VALUES('token-%q','{user:%!j}',now())",
651
zMyToken, g.zLogin
652
);
653
db_protect_pop();
654
}else if( zMyToken!=0 && P("selfdel")
655
&& fossil_strcmp(zMyToken,P("selfdel"))==0 ){
656
db_unprotect(PROTECT_CONFIG);
657
db_multi_exec(
658
"DELETE FROM config WHERE name='token-%q'",
659
zMyToken);
660
db_protect_pop();
661
zMyToken = 0;
662
}
663
if( zMyToken==0 ){
664
@ <p>You do not currently have an access token.
665
@ <a href="%R/tokens?new=true">Create one</a>
666
}else{
667
@ <p>Your access token is "%h(zMyToken)".
668
@ <p>Use this token as the value of the token= query parameter
669
@ to bypass robot defenses on unauthenticated queries to this
670
@ server (%R). Do not misuse your token. Keep it confidential.
671
@ If you misuse your token, or if somebody else steals your token
672
@ and misuses, that can result in loss of access privileges to this
673
@ server.
674
@ <p><a href="%R/tokens?selfdel=%h(zMyToken)">Delete my token</a>
675
}
676
if( g.perm.Admin ){
677
int nTok = 0;
678
Stmt s;
679
db_prepare(&s,
680
"SELECT substr(name,7), value->>'user', datetime(mtime,'unixepoch')"
681
" FROM config"
682
" WHERE name GLOB 'token-*'"
683
" AND json_valid(value,6)"
684
);
685
while( db_step(&s)==SQLITE_ROW ){
686
if( nTok==0 ){
687
@ <hr>
688
@ <p>All tokens</p>
689
@ <table border="1" cellpadding="5" cellspacing="0">
690
@ <tr><th>User <th>Token <th>Date <th> &nbsp;</tr>
691
}
692
nTok++;
693
@ <tr><td>%h(db_column_text(&s,1))
694
@ <td>%h(db_column_text(&s,0))
695
@ <td>%h(db_column_text(&s,2))
696
@ <td><a href="%R/tokens?del=%h(db_column_text(&s,0))">delete</a>
697
@ </tr>
698
}
699
db_finalize(&s);
700
if( nTok==0 ){
701
@ <hr>
702
@ <p>There are access tokens defined for this repository.
703
}else{
704
@ </table>
705
}
706
}
707
style_finish_page();
708
}
709

Keyboard Shortcuts

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