12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
...
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
...
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
...
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
...
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
...
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
...
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
...
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
...
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
...
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
...
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
...
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
...
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
...
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
...
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
...
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
|
terra http.actor_profile(co: &lib.srv.convo, actor: &lib.store.actor, meth: method.t)
var rel: lib.store.relationship
if co.aid ~= 0 then
rel = co.srv:actor_rel_calc(co.who.id, actor.id)
if meth == method.post then
var act = co:ppostv('act')
if rel.recip.block() then
if act:cmp(lib.str.plit 'follow') or act:cmp(lib.str.plit 'subscribe') then
co:complain(403,'blocked','you cannot follow a user you are blocked by') return
end
end
if act:cmp(lib.str.plit 'block') and not rel.rel.block() then
(rel.rel.block << true) ; (rel.recip.follow << false)
co.srv:actor_rel_create([lib.store.relation.idvmap.block], co.who.id, actor.id)
co.srv:actor_rel_destroy([lib.store.relation.idvmap.follow], actor.id, co.who.id)
else
[(function()
local tests = quote co:complain(400,'bad request','the action you have attempted on this user is not meaningful') return end
for i,v in ipairs(lib.store.relation.members) do
tests = quote
if [v ~= 'block'] and act:cmp(lib.str.plit([v])) and not rel.rel.[v]() then -- rely on dead code elimination :/
(rel.rel.[v] << true)
co.srv:actor_rel_create([lib.store.relation.idvmap[v]], co.who.id, actor.id)
elseif act:cmp(lib.str.plit(['un'..v])) and rel.rel.[v]() then
(rel.rel.[v] << false)
co.srv:actor_rel_destroy([lib.store.relation.idvmap[v]], co.who.id, actor.id)
else [tests] end
end
end
return tests
end)()]
................................................................................
http.actor_profile(co,actor.ptr,meth)
end
terra http.login_form(co: &lib.srv.convo, meth: method.t)
if meth_get(meth) then
-- request a username
lib.render.login(co, nil, nil, lib.str.plit(nil))
elseif meth == method.post then
var usn, usnl = co:postv('user')
var am, aml = co:postv('authmethod')
var chrs, chrsl = co:postv('response')
var cs, authok = co.srv:actor_auth_how(co.peer, usn)
var act = co.srv:actor_fetch_xid([lib.mem.ptr(int8)] {
ptr = usn, ct = usnl
})
if authok == false then
lib.render.login(co, nil, nil, lib.str.plit'access denied')
return
end
var fakeact = false
var fakeactor: lib.store.actor
if act.ptr == nil then
-- the user is known to us but has not yet claimed an
-- account on the server. create a template for the
................................................................................
}
act.ct = 1
act.ptr = &fakeactor
act.ptr.rights = lib.store.rights_default()
end
if am == nil then
-- pick an auth method
lib.render.login(co, act.ptr, &cs, lib.str.plit(nil))
else var aid: uint64 = 0
lib.dbg('authentication attempt beginning')
-- attempt login with provided method
if lib.str.ncmp('pw', am, lib.math.biggest(2,aml)) == 0 and chrs ~= nil then
aid = co.srv:actor_auth_pw(co.peer,
[lib.mem.ptr(int8)]{ptr=usn,ct=usnl},
[lib.mem.ptr(int8)]{ptr=chrs,ct=chrsl})
................................................................................
elseif lib.str.ncmp('otp', am, lib.math.biggest(2,aml)) == 0 and chrs ~= nil then
lib.dbg('using otp auth')
-- ··· --
else lib.dbg('invalid auth method') end
-- error out
if aid == 0 then
lib.render.login(co, nil, nil, lib.str.plit 'authentication failure')
else
co:installkey('/',aid)
end
end
if act.ptr ~= nil and fakeact == false then act:free() end
else
::wrongmeth:: co:complain(405, 'method not allowed', 'that method is not meaningful for this endpoint') do return end
................................................................................
end
return
elseif path(2):cmp(lib.str.lit 'del') then
if meth_get(meth) then
var conf: data.view.confirm
if post:ref() then
conf = data.view.confirm {
title = lib.str.plit 'delete post';
query = lib.str.plit 'are you sure you want to delete this post?';
cancel = lnkp
}
else
conf = data.view.confirm {
title = lib.str.plit 'cancel retweet';
query = lib.str.plit 'are you sure you want to undo this retweet?';
cancel = lib.str.plit'/';
}
end
var fr = co.srv.pool:frame()
var body = conf:poolstr(&co.srv.pool) --defer body:free()
co:stdpage([lib.srv.convo.page] {
title = lib.str.plit 'post :: delete';
class = lib.str.plit 'query';
body = body; cache = false;
})
co.srv.pool:reset(fr)
return
elseif meth == method.post then
var act = co:ppostv('act')
if act:cmp(lib.str.plit 'confirm') then
if post:ref() then
post(0).source:post_destroy(post(0).id)
elseif rt.kind ~= 0 then
co.srv:post_act_cancel(pid)
end
co:reroute('/') -- TODO maybe return to parent or conversation if possible
return
................................................................................
end
else goto badurl end
end
if post:ref() and meth == method.post then
if co.aid == 0 then goto noauth end
var act = co:ppostv('act')
if act:cmp(lib.str.plit 'like') and not co.srv:post_liked_uid(co.who.id,pid) then
co.srv:post_like(co.who.id, pid, false)
post.ptr.likes = post.ptr.likes + 1
elseif act:cmp(lib.str.plit 'dislike') and co.srv:post_liked_uid(co.who.id,pid) then
co.srv:post_like(co.who.id, pid, true)
post.ptr.likes = post.ptr.likes - 1
elseif act:cmp(lib.str.plit 'rt') then
co.srv:post_retweet(co.who.id, pid, false)
post.ptr.rts = post.ptr.rts + 1
elseif act:cmp(lib.str.plit 'post') then
var replytext = co:ppostv('post')
var acl = co:ppostv('acl')
var subj = co:ppostv('subject')
if not acl then acl = lib.str.plit 'all' end
if not replytext then goto badop end
var reply = lib.store.post {
author = co.who.id, parent = pid;
subject = subj.ptr, acl = acl.ptr, body = replytext.ptr;
}
................................................................................
::noauth:: do co:complain(401, 'unauthorized', 'you have not supplied the necessary credentials to perform this operation') return end
end
local terra
credsec_for_uid(co: &lib.srv.convo, uid: uint64)
var act = co:ppostv('act')
lib.dbg('showing credentials')
if act:cmp(lib.str.plit 'invalidate') then
lib.dbg('setting user\'s cookie validation time to now')
co.who.source:auth_sigtime_user_alter(uid, lib.osclock.time(nil))
-- the current session has been invalidated as well, so we need to immediately install a new authentication cookie with the same aid so the user doesn't need to log back in all over again
co:installkey('?',co.aid)
return
elseif act:cmp(lib.str.plit 'newcred') then
var cmt = co:ppostv('comment')
var pw = co:ppostv('newpw')
var aid: uint64 = 0
if pw:ref() then
var cpw = co:ppostv('rptpw')
if not pw:cmp(cpw) then
co:complain(400,'enrollment failure','the passwords you supplied do not match')
................................................................................
lib.dbg('setting credential restrictions')
var privs = [(function()
local check = quote end
local me = symbol(lib.store.privset)
for i,v in ipairs(lib.store.privset.members) do
check = quote [check]
var val = co:pgetv(['allow-' .. v])
if val:ref() and val:cmp(lib.str.plit'on')
then ([me].[v] << true)
else ([me].[v] << false)
end
end
end
return quote
var [me]
................................................................................
end
terra http.configure(co: &lib.srv.convo, path: hpath, meth: method.t)
var msg = pstring.null()
-- first things first, do priv checks
if path.ct >= 2 then
if not co.who.rights.powers.config() and (
path(1):cmp(lib.str.lit 'srv') or
path(1):cmp(lib.str.lit 'badge') or
path(1):cmp(lib.str.lit 'emoji')
) then goto nopriv
elseif not co.who.rights.powers.rebrand() and (
path(1):cmp(lib.str.lit 'brand')
) then goto nopriv
elseif not co.who.rights.powers.account() and (
path(1):cmp(lib.str.lit 'profile') or
path(1):cmp(lib.str.lit 'sec') or
path(1):cmp(lib.str.lit 'avi') or
path(1):cmp(lib.str.lit 'ui')
) then goto nopriv
elseif not co.who.rights.powers:affect_users() and (
path(1):cmp(lib.str.lit 'users')
) then goto nopriv end
end
................................................................................
if co.who.bio ~= nil and @co.who.bio == 0 then co.who.bio = nil end
if co.who.nym ~= nil and @co.who.nym == 0 then co.who.nym = nil end
co.who.source:actor_save(co.who)
var act = co:ppostv('act')
var resethue = false
if act:ref() then
resethue = act:cmp(lib.str.plit 'reset-hue')
end
if not resethue then
var shue = co:ppostv('hue')
var nhue, okhue = lib.math.decparse(shue)
if okhue and nhue ~= co.ui_hue then
if nhue == co.srv.cfg.ui_hue
................................................................................
end
end
if resethue then
co.srv:actor_conf_int_reset(co.who.id, 'ui-accent')
co.ui_hue = co.srv.cfg.ui_hue
end
msg = lib.str.plit 'profile changes saved'
--user_refresh = true -- not really necessary here, actually
elseif path(1):cmp(lib.str.lit 'sec') then
credsec_for_uid(co, co.who.id)
elseif path(1):cmp(lib.str.lit 'users') then
if path.ct >= 3 then
var userid, ok = lib.math.shorthand.parse(path(2).ptr, path(2).ct)
if ok then
var usr = co.srv:actor_fetch_uid(userid)
if usr:ref() then defer usr:free()
if not co.who:overpowers(usr.ptr) then goto nopriv end
end
if path.ct == 4 then
if path(3):cmp(lib.str.lit 'cred') then
credsec_for_uid(co, userid)
end
end
end
elseif path.ct == 2 and meth == method.post then
var act = co:ppostv('act')
if act:cmp(lib.str.plit'create') then
var newname = co:ppostv('handle')
if not newname or not lib.store.actor.handle_validate(newname.ptr) then
co:complain(400,'invalid handle','the handle you have requested is not valid')
end
var tu = co.srv:actor_fetch_xid(newname)
if tu:ref() then tu:free()
co:complain(409,'handle clash','that handle conflicts with one that already exists')
................................................................................
na.handle = newname.ptr
var newuid = co.srv:actor_create(&na)
var shid: int8[lib.math.shorthand.maxlen]
var shidlen = lib.math.shorthand.gen(newuid, &shid[0])
var url = lib.str.acc{}:compose('/conf/users/',pstring{&shid[0],shidlen}):finalize() defer url:free()
co:reroute(url.ptr)
return
elseif act:cmp(lib.str.plit'inst') then
else goto badop end
end
end
if user_refresh then -- refresh the user info for the renderer
var usr = co.srv:actor_fetch_uid(co.who.id)
lib.mem.heapf(co.who)
................................................................................
::nopriv:: do co:complain(403,'insufficient privileges','you do not have the necessary powers to perform this action') return end
::badop:: do co:complain(400,'bad request','the operation you have requested is not meaningful in this context') return end
end
terra http.user_notices(co: &lib.srv.convo, meth: method.t)
if meth == method.post then
var act = co:ppostv('act')
if act:cmp(lib.str.plit'clear') then
co.srv:actor_conf_int_set(co.who.id, 'notice-clear-time', lib.osclock.time(nil))
co:reroute('/')
return
else goto badop end
end
lib.render.notices(co)
................................................................................
if co.aid ~= 0 and co.who.id == uid and path.ct == 2 and path(1):cmp(lib.str.lit'upload') and co.who.rights.powers.artifact() then
if meth == method.get then
var view = data.view.media_upload {
folders = ''
}
var pg = view:poolstr(&co.srv.pool) -- defer pg:free()
co:stdpage([lib.srv.convo.page] {
title = lib.str.plit'media :: upload';
class = lib.str.plit'media upload';
cache = false; body = pg;
})
elseif meth == method.post_file then
var desc = pstring.null()
var folder = pstring.null()
var mime = pstring.null()
var name = pstring.null()
var body = binblob.null()
for i=0, co.uploads.sz do var up = co.uploads.storage.ptr + i
if up.body.ct > 0 then
if up.field:cmp(lib.str.plit'desc') then
desc = up.body
elseif up.field:cmp(lib.str.plit'folder') then
folder = up.body
elseif up.field:cmp(lib.str.plit'file') then
mime = up.ctype
body = binblob {ptr = [&uint8](up.body.ptr), ct = up.body.ct}
name = up.filename
end
end
end
if not body then goto badop end
................................................................................
var url = lib.str.acc{}:compose('/media/a/',pstring{&idbuf[0],idlen}):finalize()
co:reroute(url.ptr)
url:free()
else goto badop end
elseif co.aid ~= 0 and path.ct == 4 and path(1):cmp(lib.str.lit'a') and meth==method.post then
var act = co:ppostv('act')
if not act or not act:cmp(lib.str.plit'confirm') then goto badop end
var artid, aok = lib.math.shorthand.parse(path(2).ptr,path(2).ct)
if not aok then goto e404 end
var art = co.srv:artifact_fetch(uid,artid)
if not art then goto e404 end
defer art:free()
if path(3):cmp(lib.str.lit'avi') then
................................................................................
elseif uri.ptr[1] == @'s' and uri.ptr[2] == @'/' and uri.ct > 3 then
if not meth_get(meth) then goto wrongmeth end
if not http.static_content(co, uri.ptr + 3, uri.ct - 3) then goto notfound end
elseif lib.str.ncmp('/avi/', uri.ptr, 5) == 0 then
http.local_avatar(co, [lib.mem.ptr(int8)] {ptr = uri.ptr + 5, ct = uri.ct - 5})
elseif lib.str.ncmp('/file/', uri.ptr, 6) == 0 then
http.file_serve_raw(co, [lib.mem.ptr(int8)] {ptr = uri.ptr + 6, ct = uri.ct - 6})
elseif uri:cmp(lib.str.plit '/notices') then
if co.aid == 0 then co:reroute('/login') return end
http.user_notices(co,meth)
elseif uri:cmp(lib.str.plit '/compose') then
if co.aid == 0 then co:reroute('/login') return end
http.post_compose(co,meth)
elseif uri:cmp(lib.str.plit '/login') then
if co.aid == 0
then http.login_form(co, meth)
else co:reroute('/')
end
elseif uri:cmp(lib.str.plit '/logout') then
if co.aid == 0
then goto notfound
else co:reroute_cookie('/','auth=; Path=/')
end
else -- hierarchical routes
var path = lib.http.hier(&co.srv.pool, uri) --defer path:free()
if path.ct > 1 and path(0):cmp(lib.str.lit('user')) then
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
...
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
...
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
...
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
...
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
...
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
...
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
...
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
...
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
...
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
...
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
...
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
...
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
...
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
...
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
...
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
|
terra http.actor_profile(co: &lib.srv.convo, actor: &lib.store.actor, meth: method.t)
var rel: lib.store.relationship
if co.aid ~= 0 then
rel = co.srv:actor_rel_calc(co.who.id, actor.id)
if meth == method.post then
var act = co:ppostv('act')
if rel.recip.block() then
if act:cmp( 'follow') or act:cmp( 'subscribe') then
co:complain(403,'blocked','you cannot follow a user you are blocked by') return
end
end
if act:cmp( 'block') and not rel.rel.block() then
(rel.rel.block << true) ; (rel.recip.follow << false)
co.srv:actor_rel_create([lib.store.relation.idvmap.block], co.who.id, actor.id)
co.srv:actor_rel_destroy([lib.store.relation.idvmap.follow], actor.id, co.who.id)
else
[(function()
local tests = quote co:complain(400,'bad request','the action you have attempted on this user is not meaningful') return end
for i,v in ipairs(lib.store.relation.members) do
tests = quote
if [v ~= 'block'] and act:cmp(([v])) and not rel.rel.[v]() then -- rely on dead code elimination :/
(rel.rel.[v] << true)
co.srv:actor_rel_create([lib.store.relation.idvmap[v]], co.who.id, actor.id)
elseif act:cmp((['un'..v])) and rel.rel.[v]() then
(rel.rel.[v] << false)
co.srv:actor_rel_destroy([lib.store.relation.idvmap[v]], co.who.id, actor.id)
else [tests] end
end
end
return tests
end)()]
................................................................................
http.actor_profile(co,actor.ptr,meth)
end
terra http.login_form(co: &lib.srv.convo, meth: method.t)
if meth_get(meth) then
-- request a username
lib.render.login(co, nil, nil, pstring.null())
elseif meth == method.post then
var usn, usnl = co:postv('user')
var am, aml = co:postv('authmethod')
var chrs, chrsl = co:postv('response')
var cs, authok = co.srv:actor_auth_how(co.peer, usn)
var act = co.srv:actor_fetch_xid([lib.mem.ptr(int8)] {
ptr = usn, ct = usnl
})
if authok == false then
lib.render.login(co, nil, nil, 'access denied')
return
end
var fakeact = false
var fakeactor: lib.store.actor
if act.ptr == nil then
-- the user is known to us but has not yet claimed an
-- account on the server. create a template for the
................................................................................
}
act.ct = 1
act.ptr = &fakeactor
act.ptr.rights = lib.store.rights_default()
end
if am == nil then
-- pick an auth method
lib.render.login(co, act.ptr, &cs, pstring.null())
else var aid: uint64 = 0
lib.dbg('authentication attempt beginning')
-- attempt login with provided method
if lib.str.ncmp('pw', am, lib.math.biggest(2,aml)) == 0 and chrs ~= nil then
aid = co.srv:actor_auth_pw(co.peer,
[lib.mem.ptr(int8)]{ptr=usn,ct=usnl},
[lib.mem.ptr(int8)]{ptr=chrs,ct=chrsl})
................................................................................
elseif lib.str.ncmp('otp', am, lib.math.biggest(2,aml)) == 0 and chrs ~= nil then
lib.dbg('using otp auth')
-- ··· --
else lib.dbg('invalid auth method') end
-- error out
if aid == 0 then
lib.render.login(co, nil, nil, 'authentication failure')
else
co:installkey('/',aid)
end
end
if act.ptr ~= nil and fakeact == false then act:free() end
else
::wrongmeth:: co:complain(405, 'method not allowed', 'that method is not meaningful for this endpoint') do return end
................................................................................
end
return
elseif path(2):cmp(lib.str.lit 'del') then
if meth_get(meth) then
var conf: data.view.confirm
if post:ref() then
conf = data.view.confirm {
title = 'delete post';
query = 'are you sure you want to delete this post?';
cancel = lnkp
}
else
conf = data.view.confirm {
title = 'cancel retweet';
query = 'are you sure you want to undo this retweet?';
cancel = '/';
}
end
var fr = co.srv.pool:frame()
var body = conf:poolstr(&co.srv.pool) --defer body:free()
co:stdpage([lib.srv.convo.page] {
title = 'post :: delete';
class = 'query';
body = body; cache = false;
})
co.srv.pool:reset(fr)
return
elseif meth == method.post then
var act = co:ppostv('act')
if act:cmp( 'confirm') then
if post:ref() then
post(0).source:post_destroy(post(0).id)
elseif rt.kind ~= 0 then
co.srv:post_act_cancel(pid)
end
co:reroute('/') -- TODO maybe return to parent or conversation if possible
return
................................................................................
end
else goto badurl end
end
if post:ref() and meth == method.post then
if co.aid == 0 then goto noauth end
var act = co:ppostv('act')
if act:cmp( 'like') and not co.srv:post_liked_uid(co.who.id,pid) then
co.srv:post_like(co.who.id, pid, false)
post.ptr.likes = post.ptr.likes + 1
elseif act:cmp( 'dislike') and co.srv:post_liked_uid(co.who.id,pid) then
co.srv:post_like(co.who.id, pid, true)
post.ptr.likes = post.ptr.likes - 1
elseif act:cmp( 'rt') then
co.srv:post_retweet(co.who.id, pid, false)
post.ptr.rts = post.ptr.rts + 1
elseif act:cmp( 'post') then
var replytext = co:ppostv('post')
var acl = co:ppostv('acl')
var subj = co:ppostv('subject')
if not acl then acl = 'all' end
if not replytext then goto badop end
var reply = lib.store.post {
author = co.who.id, parent = pid;
subject = subj.ptr, acl = acl.ptr, body = replytext.ptr;
}
................................................................................
::noauth:: do co:complain(401, 'unauthorized', 'you have not supplied the necessary credentials to perform this operation') return end
end
local terra
credsec_for_uid(co: &lib.srv.convo, uid: uint64)
var act = co:ppostv('act')
lib.dbg('showing credentials')
if act:cmp( 'invalidate') then
lib.dbg('setting user\'s cookie validation time to now')
co.who.source:auth_sigtime_user_alter(uid, lib.osclock.time(nil))
-- the current session has been invalidated as well, so we need to immediately install a new authentication cookie with the same aid so the user doesn't need to log back in all over again
co:installkey('?',co.aid)
return
elseif act:cmp( 'newcred') then
var cmt = co:ppostv('comment')
var pw = co:ppostv('newpw')
var aid: uint64 = 0
if pw:ref() then
var cpw = co:ppostv('rptpw')
if not pw:cmp(cpw) then
co:complain(400,'enrollment failure','the passwords you supplied do not match')
................................................................................
lib.dbg('setting credential restrictions')
var privs = [(function()
local check = quote end
local me = symbol(lib.store.privset)
for i,v in ipairs(lib.store.privset.members) do
check = quote [check]
var val = co:pgetv(['allow-' .. v])
if val:ref() and val:cmp('on')
then ([me].[v] << true)
else ([me].[v] << false)
end
end
end
return quote
var [me]
................................................................................
end
terra http.configure(co: &lib.srv.convo, path: hpath, meth: method.t)
var msg = pstring.null()
-- first things first, do priv checks
if path.ct >= 2 then
if not co.who.rights.powers.config() and (
path(1):cmp('srv') or
path(1):cmp('badge') or
path(1):cmp('emoji')
) then goto nopriv
elseif not co.who.rights.powers.rebrand() and (
path(1):cmp('brand')
) then goto nopriv
elseif not co.who.rights.powers.account() and (
path(1):cmp('profile') or
path(1):cmp('sec') or
path(1):cmp('avi') or
path(1):cmp('ui')
) then goto nopriv
elseif not co.who.rights.powers:affect_users() and (
path(1):cmp(lib.str.lit 'users')
) then goto nopriv end
end
................................................................................
if co.who.bio ~= nil and @co.who.bio == 0 then co.who.bio = nil end
if co.who.nym ~= nil and @co.who.nym == 0 then co.who.nym = nil end
co.who.source:actor_save(co.who)
var act = co:ppostv('act')
var resethue = false
if act:ref() then
resethue = act:cmp( 'reset-hue')
end
if not resethue then
var shue = co:ppostv('hue')
var nhue, okhue = lib.math.decparse(shue)
if okhue and nhue ~= co.ui_hue then
if nhue == co.srv.cfg.ui_hue
................................................................................
end
end
if resethue then
co.srv:actor_conf_int_reset(co.who.id, 'ui-accent')
co.ui_hue = co.srv.cfg.ui_hue
end
msg = 'profile changes saved'
--user_refresh = true -- not really necessary here, actually
elseif path(1):cmp('sec') then
credsec_for_uid(co, co.who.id)
elseif path(1):cmp('users') then
if path.ct >= 3 then
var userid, ok = lib.math.shorthand.parse(path(2).ptr, path(2).ct)
if ok then
var usr = co.srv:actor_fetch_uid(userid)
if usr:ref() then --defer usr:free()
if not co.who:overpowers(usr.ptr) then
usr:free()
goto nopriv
end
else goto badop end
defer usr:free()
if path.ct == 4 then
if path(3):cmp(lib.str.lit 'cred') then
credsec_for_uid(co, userid)
end
elseif path.ct == 3 then
var purgestr = co:ppostv("purgestr")
var purgekey = co:ppostv("purgekey")
if purgestr:ref() and purgekey:ref() and purgestr(0) ~= 0 then
if purgestr:cmp(purgekey) then -- destroying account! :O
co.srv:actor_purge_uid(userid)
co:reroute('/conf/users')
return
else msg = 'purge confirmation failed' end
end
var epithet = co:ppostv("epithet")
var s_rank = co:ppostv("rank")
var s_invites = co:ppostv("invites")
var s_quota = co:ppostv("quota")
var ch_staff = co:ppostv("staff")
var torank: uint16 = usr(0).rights.rank
if ch_staff:ref() and ch_staff:cmp('on') then
if s_rank:ref() then
var rank, rok = lib.math.decparse(s_rank)
if rok and rank <= co.srv.cfg.nranks then
torank = rank
end
elseif usr(0).rights.rank == 0 then
torank = co.who.rights.rank + 1
end
else torank = 0 end
if co.who.id ~= userid and co.who.rights.rank > 0 then
if (co.who.rights.powers.elevate() and
(torank < usr(0).rights.rank or usr(0).rights.rank == 0) and
(torank > co.who.rights.rank or co.who.rights.rank == 1))
or (co.who.rights.powers.demote() and
(torank > usr(0).rights.rank or torank == 0))
then usr(0).rights.rank = torank end
end
if s_invites:ref() then
var n_invites, n_invites_ok = lib.math.decparse(s_invites)
if n_invites_ok and n_invites ~= usr(0).rights.invites then
if (n_invites > usr(0).rights.invites and
co.who.rights.powers.elevate() and
co.who.rights.powers.invite())
or (n_invites < usr(0).rights.invites and
co.who.rights.powers.demote())
then usr(0).rights.invites = n_invites end
end
end
if (co.who.id ~= userid or co.who.rights.rank == 1) and s_quota:ref() then
var n_quota, n_quota_ok = lib.math.decparse(s_quota)
if n_quota_ok and n_quota ~= usr(0).rights.quota then
if (co.who.rights.powers.elevate() and
((n_quota == 0 and co.who.rights.quota == 0 or co.who.rights.rank == 1) or
(n_quota ~= 0 and (n_quota > usr(0).rights.quota and
(co.who.rights.quota == 0 or
co.who.rights.quota >= n_quota or
co.who.rights.rank == 1)))))
or (co.who.rights.powers.demote() and n_quota ~= 0 and
(n_quota < usr(0).rights.quota or
co.who.rights.rank == 1))
then usr(0).rights.quota = n_quota end
end
end
if co.who.rights.powers.herald() and
(co.who.id ~= userid or
co.srv.cfg.pol_autoherald or
co.who.rights.rank == 1) then
if epithet:ref() and epithet(0) ~= 0 then
usr(0).epithet = epithet.ptr
else
usr(0).epithet = nil
end
end
if co.who.id ~= userid then
-- update powers
end
co.srv:actor_save(usr.ptr)
if not msg then msg = 'user record updated' end
end
end
elseif path.ct == 2 and meth == method.post then
var act = co:ppostv('act')
if act:cmp('create') then
var newname = co:ppostv('handle')
if not newname or not lib.store.actor.handle_validate(newname.ptr) then
co:complain(400,'invalid handle','the handle you have requested is not valid')
end
var tu = co.srv:actor_fetch_xid(newname)
if tu:ref() then tu:free()
co:complain(409,'handle clash','that handle conflicts with one that already exists')
................................................................................
na.handle = newname.ptr
var newuid = co.srv:actor_create(&na)
var shid: int8[lib.math.shorthand.maxlen]
var shidlen = lib.math.shorthand.gen(newuid, &shid[0])
var url = lib.str.acc{}:compose('/conf/users/',pstring{&shid[0],shidlen}):finalize() defer url:free()
co:reroute(url.ptr)
return
elseif act:cmp('inst') then
else goto badop end
end
end
if user_refresh then -- refresh the user info for the renderer
var usr = co.srv:actor_fetch_uid(co.who.id)
lib.mem.heapf(co.who)
................................................................................
::nopriv:: do co:complain(403,'insufficient privileges','you do not have the necessary powers to perform this action') return end
::badop:: do co:complain(400,'bad request','the operation you have requested is not meaningful in this context') return end
end
terra http.user_notices(co: &lib.srv.convo, meth: method.t)
if meth == method.post then
var act = co:ppostv('act')
if act:cmp('clear') then
co.srv:actor_conf_int_set(co.who.id, 'notice-clear-time', lib.osclock.time(nil))
co:reroute('/')
return
else goto badop end
end
lib.render.notices(co)
................................................................................
if co.aid ~= 0 and co.who.id == uid and path.ct == 2 and path(1):cmp(lib.str.lit'upload') and co.who.rights.powers.artifact() then
if meth == method.get then
var view = data.view.media_upload {
folders = ''
}
var pg = view:poolstr(&co.srv.pool) -- defer pg:free()
co:stdpage([lib.srv.convo.page] {
title = 'media :: upload';
class = 'media upload';
cache = false; body = pg;
})
elseif meth == method.post_file then
var desc = pstring.null()
var folder = pstring.null()
var mime = pstring.null()
var name = pstring.null()
var body = binblob.null()
for i=0, co.uploads.sz do var up = co.uploads.storage.ptr + i
if up.body.ct > 0 then
if up.field:cmp('desc') then
desc = up.body
elseif up.field:cmp('folder') then
folder = up.body
elseif up.field:cmp('file') then
mime = up.ctype
body = binblob {ptr = [&uint8](up.body.ptr), ct = up.body.ct}
name = up.filename
end
end
end
if not body then goto badop end
................................................................................
var url = lib.str.acc{}:compose('/media/a/',pstring{&idbuf[0],idlen}):finalize()
co:reroute(url.ptr)
url:free()
else goto badop end
elseif co.aid ~= 0 and path.ct == 4 and path(1):cmp(lib.str.lit'a') and meth==method.post then
var act = co:ppostv('act')
if not act or not act:cmp('confirm') then goto badop end
var artid, aok = lib.math.shorthand.parse(path(2).ptr,path(2).ct)
if not aok then goto e404 end
var art = co.srv:artifact_fetch(uid,artid)
if not art then goto e404 end
defer art:free()
if path(3):cmp(lib.str.lit'avi') then
................................................................................
elseif uri.ptr[1] == @'s' and uri.ptr[2] == @'/' and uri.ct > 3 then
if not meth_get(meth) then goto wrongmeth end
if not http.static_content(co, uri.ptr + 3, uri.ct - 3) then goto notfound end
elseif lib.str.ncmp('/avi/', uri.ptr, 5) == 0 then
http.local_avatar(co, [lib.mem.ptr(int8)] {ptr = uri.ptr + 5, ct = uri.ct - 5})
elseif lib.str.ncmp('/file/', uri.ptr, 6) == 0 then
http.file_serve_raw(co, [lib.mem.ptr(int8)] {ptr = uri.ptr + 6, ct = uri.ct - 6})
elseif uri:cmp( '/notices') then
if co.aid == 0 then co:reroute('/login') return end
http.user_notices(co,meth)
elseif uri:cmp( '/compose') then
if co.aid == 0 then co:reroute('/login') return end
http.post_compose(co,meth)
elseif uri:cmp( '/login') then
if co.aid == 0
then http.login_form(co, meth)
else co:reroute('/')
end
elseif uri:cmp( '/logout') then
if co.aid == 0
then goto notfound
else co:reroute_cookie('/','auth=; Path=/')
end
else -- hierarchical routes
var path = lib.http.hier(&co.srv.pool, uri) --defer path:free()
if path.ct > 1 and path(0):cmp(lib.str.lit('user')) then
|