@bkazemi/verily 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +503 -0
- package/dist/browser/badge.d.ts +39 -0
- package/dist/browser/badge.js +440 -0
- package/dist/browser/connect-dialog.d.ts +83 -0
- package/dist/browser/connect-dialog.js +514 -0
- package/dist/browser/evidence-dialog.d.ts +59 -0
- package/dist/browser/evidence-dialog.js +541 -0
- package/dist/browser/index.d.ts +59 -0
- package/dist/browser/index.js +809 -0
- package/dist/browser/logo.d.ts +2 -0
- package/dist/browser/logo.js +17 -0
- package/dist/browser/mark.d.ts +19 -0
- package/dist/browser/mark.js +77 -0
- package/dist/browser/provider-mark.d.ts +6 -0
- package/dist/browser/provider-mark.js +99 -0
- package/dist/browser/signed.d.ts +40 -0
- package/dist/browser/signed.js +219 -0
- package/dist/browser.d.ts +3 -0
- package/dist/browser.js +3 -0
- package/dist/core/index.d.ts +454 -0
- package/dist/core/index.js +128 -0
- package/dist/core/signed.d.ts +49 -0
- package/dist/core/signed.js +113 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/logo.d.ts +21 -0
- package/dist/logo.js +38 -0
- package/dist/postgres/index.d.ts +12 -0
- package/dist/postgres/index.js +76 -0
- package/dist/server/addresses.d.ts +2 -0
- package/dist/server/addresses.js +61 -0
- package/dist/server/body.d.ts +11 -0
- package/dist/server/body.js +49 -0
- package/dist/server/copy.d.ts +8 -0
- package/dist/server/copy.js +42 -0
- package/dist/server/discord.d.ts +7 -0
- package/dist/server/discord.js +69 -0
- package/dist/server/email-logo.d.ts +11 -0
- package/dist/server/email-logo.js +122 -0
- package/dist/server/email.d.ts +46 -0
- package/dist/server/email.js +145 -0
- package/dist/server/escape.d.ts +2 -0
- package/dist/server/escape.js +2 -0
- package/dist/server/github-gist.d.ts +11 -0
- package/dist/server/github-gist.js +68 -0
- package/dist/server/github.d.ts +7 -0
- package/dist/server/github.js +65 -0
- package/dist/server/index.d.ts +70 -0
- package/dist/server/index.js +862 -0
- package/dist/server/link.d.ts +64 -0
- package/dist/server/link.js +458 -0
- package/dist/server/openpgp.d.ts +49 -0
- package/dist/server/openpgp.js +158 -0
- package/dist/server/pgp.d.ts +29 -0
- package/dist/server/pgp.js +198 -0
- package/dist/server/public-fetch.d.ts +23 -0
- package/dist/server/public-fetch.js +62 -0
- package/dist/server/resend.d.ts +13 -0
- package/dist/server/resend.js +37 -0
- package/dist/server/service.d.ts +311 -0
- package/dist/server/service.js +1178 -0
- package/dist/server/signing.d.ts +16 -0
- package/dist/server/signing.js +56 -0
- package/dist/server/style.d.ts +17 -0
- package/dist/server/style.js +244 -0
- package/dist/server/wkd.d.ts +7 -0
- package/dist/server/wkd.js +65 -0
- package/dist/server/youtube.d.ts +11 -0
- package/dist/server/youtube.js +99 -0
- package/dist/site/index.d.ts +139 -0
- package/dist/site/index.js +239 -0
- package/dist/verily.js +11907 -0
- package/dist/version.d.ts +10 -0
- package/dist/version.js +10 -0
- package/docs/licenses/gpl-3.0.txt +232 -0
- package/docs/licenses/octicons.txt +21 -0
- package/docs/licenses/openpgp.txt +172 -0
- package/docs/licenses/rubik.txt +93 -0
- package/docs/licenses/simple-icons.txt +8 -0
- package/package.json +86 -0
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Verily logotype on its plate, as a PNG, for mail. A message cannot draw the logotype
|
|
3
|
+
* the way a page does: the clients most people read mail in strip an inline SVG. So it
|
|
4
|
+
* travels inside the message as an image of its own, which is shown without anything
|
|
5
|
+
* being fetched.
|
|
6
|
+
*
|
|
7
|
+
* This is docs/design/verily-logo-plate.svg drawn at 281 by 120, three times the 94 by 40
|
|
8
|
+
* it is shown at, with the plate's corners left transparent. Regenerate it from that file
|
|
9
|
+
* if the logotype changes.
|
|
10
|
+
*/
|
|
11
|
+
export const emailLogo = 'iVBORw0KGgoAAAANSUhEUgAAARkAAAB4CAYAAADGz4ZcAAAABmJLR0QA/wD/AP+gvaeTAAAchUlEQVR4nO2dd3wU' +
|
|
12
|
+
'VdfHf7Obzab3DklIoQQISJEnVHnoIKhUUVqAUEREsePr86qoqA+W10YnoYMgKEWUkpBIr+mNkFACKZtKyiab7O68' +
|
|
13
|
+
'f2CUkGTmZndmS7jfz4fPR2ZO7jnrkjN37mkMCLF19gyVAOMBDAQQAsAdgD3pz1MoFLNFA6AMDG4DSGJYNtqCUR8u' +
|
|
14
|
+
'LS2tIPlhhk/A0cVjlFbLfAwG/fS1lEKhtBlqGLBRGhlWVisUhVyCLToZezefzqxa8z3DYKTw9lEolDZCFcuyq6vK' +
|
|
15
|
+
'FasAqJsTaNbJOLp4jNKyzE8AnMS0jkKhtBXYP9l6yZSqqoKiR+80cTL2Lp4LwWINAKlBbKNQKG0DBllqSIfXlObl' +
|
|
16
|
+
'Nr78EH/tYH4DYGFQ4ygUSpuAARKsLTGosLCwuuGapOE/7N18OmtZZi+og6FQKDrCAk/U1CHq4Wt/vxJZWdrsAYOu' +
|
|
17
|
+
'hjeLQqG0MbrJrezi62qrM4G/djIOLh6jWQYjjGsXhUJpMzDsf/HXW5EUACzldrvAoJ1RjaJQKG0Ixs3KyjZbVVud' +
|
|
18
|
+
'KLFz9uxOE+0oFIrQsAwzFwAkDDDB2MZQKJQ2yWBHRz9nCR7UIlEoFIrQSFlJXZgEQBdjW0KhUNomLKMNkQDwMrYh' +
|
|
19
|
+
'FAqljcLCUwLAxth2UCiUtgkD2ElA0O6BQqFQdEPCSPiFhMVGIoW3XG5otXoRbE03exSKrhisTslWKsVEd3fM9PTB' +
|
|
20
|
+
'dWU1lmVlGkq1XrSTyxEV0hU5NTXYoyjEsdISaFnW2GY91ixcMA8r3n4Trq4uTe5ptVqkpKZhydLXkJScotP6Hu7u' +
|
|
21
|
+
'6Ny5Iyyk//x6qDVqZGZmQVHUpJMBhQfG3tlT1N8YZ5kMk9zcMd3TC7bSf7pHLMpIR3J1lZiqBWFFhw6Y4Or+999z' +
|
|
22
|
+
'amuws6AAx0tLoKHOxuD4+fki8eoFSKXcnUgSk5IxeGjr+61NGD8OkRvXQS63bHJPparD3IhFOPLb761e93GFAbNW' +
|
|
23
|
+
'tNclL0tLLPf1w4HQHpjv066RgwGAmV7eYqkWDA+ZDGOc3RpdC7Syxn86BGBft1BM8/SE3PBvnI81QYGBvA4GALqG' +
|
|
24
|
+
'6JaZsfzVpc06GACQyy2xZPFCndZ9nBH8dclZJsPSdu0xysUVUqblM+VBTk4IsrZGdk2N0CYIxgwvb8gkzX8GL7kc' +
|
|
25
|
+
'r7X3wwxPL2zNz8eBIoWBrXs8kUjInDrD8W+PC2cn7maQTk6OOq37OCP4Y1ipVqOfoyOngwEehLRmm/BuxsnCAhPc' +
|
|
26
|
+
'3Hnl3GWWaG9lXgfZFIohEdzJqFgW+wo5m5f/zXBnF/haWQltgiBM9/SCFcFTs07LYncB2eelUB5HRDlQ2F+kQJWm' +
|
|
27
|
+
'2cbljZUzDF70NL2EY1upFJPc+XcxAHC0tAhF9XUiW0ShmC+iOJlqjQY/K8jOKJ52dYOnZfMHbcZimocn7KT8x1Va' +
|
|
28
|
+
'lsVuwl0bRRiUSiWRXHV1Nb8QxSCIFhr5SVGIGq2GV86CYTDdw3R2M3K5DLYDgqGS8h8cniwrRW5trQGsojQQH5+A' +
|
|
29
|
+
'zOtZvHJbt+80gDUUEkRLxruvVuNQUTGe9/TklX3O3R3bCvNRVl8vljnEhA7tho19nLG3uxbP5VRjclY17Ou1TeRY' +
|
|
30
|
+
'ANsK8g1v4GNOrUqFYSPHYfzTY+Hl6dHkvlqtQUpKKk7F/WkE6yjNIWrG7+7CfEx094BlC2HgBuQSCaa6e2BD3j0x' +
|
|
31
|
+
'zeHFwkKK/L4PupDel0uwNcQe+4LtMOa2Ei9mVsG19p+d2enyMuSYcPi9LVNZWYnde/Ya2wwKIaJmkinq6/F7aTGR' +
|
|
32
|
+
'7FTCcxAx6Tk4BMW2jW1QyhgcCLbFC2M98FVvRyisHySC7SgoMIaJFIrZIXq66vaCfKL0+9ZEdMRAIpWgKMy/xfv1' +
|
|
33
|
+
'EgZHAmwxc4wnvvGVIMUMSiIoFFNAdCeTp1IhpqyUSPYFwtwUMQgN64RCe/6dVL0ESMqiZzEUCikG+Y3ekp9PVLns' +
|
|
34
|
+
'SJhlKzQMA1QOCCCSDSiuRWbqHZEtolDaDgZxMjdra3D2/n0iWa56IbHo1rcj7jmT5epIY/nDpxQK5R8M9m6yNT+P' +
|
|
35
|
+
'SK65ymexUQ8KJJJrX6ZCWnyOyNZQKG0Lg4Vz0pTVuFJRgb4ODryys729cbS02CD9WrqE+uOmO1n9lN2ZHIhtkpVc' +
|
|
36
|
+
'ji4hndG+XTvY2dnB3s4WjESC6upqVFVWo7ikGJnXs1BcXCKuIRSKQBg0ZrytMJ/IybSTyzHMyRknCA+M9UH6705E' +
|
|
37
|
+
'ch6V9Ui+dENw/RYWFhg4IAwTnh6HoUOHICgwgKhfSkFhIZKSUnD092M4/NtRFBWRpQroilQqxbSpk9G5U8dGbRRq' +
|
|
38
|
+
'ampw4mQMrl6L513D17c9OgYHw9/PF05OjrC2tob8oVasSqUSfxw/gcTEZN61evYMxZhRI2Fj07g1alFRMfbs3WcS' +
|
|
39
|
+
'TjgoKBATn50Ae3v7RterqqpwIjoGCQlJRrFr5IhheLJvn0b/7wEgJ+cmdv+0F3V1wibFit4Z71HWde6CHnb2vHI3' +
|
|
40
|
+
'apSYk5YKMY0L6uSDu+FPEsl2O3YD1+JSBdNtZ2eHiHlzsPTlxfDQM3Sv0WgQcyoOn33xJa5cvSaQhY359pvVmDtn' +
|
|
41
|
+
'VrP3tFotJk+bgeiYU03u9erVE3NmzcDI4cPg69ueV49arcawUeM4fwGfeKIHYo4fhYVF88/InJybCBs4FLUqFa++' +
|
|
42
|
+
'R4m/fA5BQS2/PqekpmHA4GG867i4OCP+8nk4Ozffn6a+vh4jx0zAtfiEVtuoDy++MA3rfvyuxfu7du/F4peXCaZP' +
|
|
43
|
+
'1M54LbGjkCyJLdjaBgN5Ggjpi90wsu5prko1ks+nC6KTYRjMnTMLqYlXsPLD/+jtYIAHu4yRI4Yh5sRR7N29Hf7+' +
|
|
44
|
+
'fgJY2pgpk55r8Z5EIsH0aZMbXQvt3g1HDu5HXPQxzAufTeRggAc7u7GjR3HKjB09qkUHAwCBgQEI6WrcmYUdg4Nb' +
|
|
45
|
+
'dDAAIJPJsOyVJQa0CLC2ssKqjz/ilOnVq6fgeg3uZM6Wl+M6YSXtHC8f0ezw9/dApi//jgoAvC7mor6uaf1Sa/Hx' +
|
|
46
|
+
'9sav+/fg229Wc/4D1Icxo0fibNxJTqegC7a2tsT3Fy2cj9joPzBksG4TkK14egw9us1vDmsj9ylKSExEVRV3wuaE' +
|
|
47
|
+
'p8fC28twxcFTp0yCi4szp8zpM+cE12twJ8MC2ElYWNjN1pboDEcXXEZ2BUsQKXes1SA1Vv9dTEiXzog+cRT/HvqU' +
|
|
48
|
+
'3mvx4eDggMhN6/DRB++LrutR3n/vHaz+/FPIZDKD6zYlVKo6HDz0G6eMTCZD+JyZBrLogfPn4+f9vwiu1yjptdHl' +
|
|
49
|
+
'ZchVkbVImCVCi05vb2dkBpA5L7+reVDpWR3eq1dPHDt6CO18DNtudPmrS/HJyv81mL45s2bg7TeXG0yfqbN+02Ze' +
|
|
50
|
+
'mfA5Mw3ikAcN7I/Q7t04ZeLjE3Hh4iXBdRulIlHLstiRX4AVHTrwyj5p74BQWztBx6f4jOyGUoJG07b1WqTH6nfY' +
|
|
51
|
+
'287HGz/t2taqBtTJKam4Fp+A7Owc3L9fAa1WCwcHB/j7++GJnqHo07sXUQQKAJYtXQKFohjf/bBG149AhJ+fLz5f' +
|
|
52
|
+
'tVKQterq2kanwYSEJFy6fAX9nuzbooy3lxfGjxuDXw4eFtUWkl3M2g2bRNFttLLn30uLMdfHB14EXfFmennhnWxh' +
|
|
53
|
+
'wsdubvbI7ORKJBuUUIjL1a2PUDQgl1tiz85t8CLoqVNXV4+NmyOxKXIrsrO5E/68vbww48Xn8eorS+DoyO+8PvjP' +
|
|
54
|
+
'Cpy/cBGXr1wltr219AjtLsg6Wq0WJ6ObRqnMlfUbIzmdDAAsiJgnqpNp374dnh47hlOmUKHAgV9+FUW/0ZyMmmWx' +
|
|
55
|
+
'u7AAy335IyGDnJwFG5/SYWQokgleEq3ULLJidJtA2MDyV19Bz56hvHIJCUmYu2Axr3NpIL+gAF9+/S0it2zDd998' +
|
|
56
|
+
'iWcmPM0pL5PJELlxLfr1H4IaI3XyKykpxaXLV1BUXIzS0jKwzWQ1KpVKnDgZY/CwrpgcPHQYqz75EJ4eTRtsNTBo' +
|
|
57
|
+
'YH90DemCtPQMUWxYMH8uZzQOACKjtgmeH9OAURu4HCpSYLa3N1wtuN9JG8anfHBTv5R+JwcbZIWQlSx0SlXg8n2y' +
|
|
58
|
+
'KFhzBAcF4fXl/PkGhw7/hoiFS3TK6SgtLcOs8Ai8+/YbWPHOm5yy/v5+WLwoAt98+0Or9eiKWq3Gvp8PYM36jUhO' +
|
|
59
|
+
'ToVWq3+Eztyoq6tH1JbtePftNzjlIuaH4/U33xVcv7WVFebMmsEpo1LVYXPkVsF1N2DU8YeGHp/SaWQoai34z2Jk' +
|
|
60
|
+
'WhY5J9P00vU/770NK55Q66nYOMxbsFgnB9MAy7L47Isv8dU3LSdYNfD6a6+IFjp/lJycmxj01AgsWrIMiYnJj6WD' +
|
|
61
|
+
'aSBqy3bU8wQPpk+b0iQzWAhIwtb7D/wq6oxvo89YNdT4FFtbOW704D8bAYAuGaUoKanQWVdAQAc898x4TplChQIR' +
|
|
62
|
+
'i14WbIv6yaov8Ofps5wyjo6OmBc+WxB9XFyLT8DQEWNE2/6bG/kFBTh05CinjJ2dHV6YPlVw3WQHvhsF1/swRncy' +
|
|
63
|
+
'hhqf0m14KKpl/LsYCQvkxei3i3n5pUW80Z+PPl4laL2RRqPBq6+/xfvEnPnidJ1HuJJQVFSMGbPmorycrLXH48KG' +
|
|
64
|
+
'jZG8MhHzwgX9bkjC1ufOXyCqFdMHozsZQPzxKXK5DDd7keWodM0uR16e7oWZUqkUE5+dwCmTk3MTu/fs01lHS2Rn' +
|
|
65
|
+
'52Dbjl2cMkFBgegf1k9w3Q28/8FK3MujnQMf5fyFi0hK5g4kdOncCYMHDRBMJ8kuZt16ccLWD2MSTqZhfAoJz7m7' +
|
|
66
|
+
'w0nWuvPq0KHdUCHn/6gMCyii9dvFDBwQBnd37sPlrdt3QqPhd6q6QHKAN27saFF03759B/t+PiDK2m0Bkt3Mgoi5' +
|
|
67
|
+
'gugiCVvn5t7FkaN/CKKPC5NwMsCD8Sl1Wv6a63vOlvhXf/LiNwuZBHlPkhXndb5Tidzb+h2AjR3DXdwHAPt/OaiX' +
|
|
68
|
+
'Di5SUtOQnpHJKTNk0CBRdP9y8DDUav7ztceVfT8fQGlpGafM02PHCJIZThK23rg5yiDfl8k4Gb7xKVlOMnwY5oyI' +
|
|
69
|
+
'4R64NCIAtrZkkaaeg7qixIYsO7bypP41Sn379Oa8f+vWbdy5k6u3Hi5OnYrjvB8a2o0oia+18B08P+7U1NZi+87d' +
|
|
70
|
+
'nDIWFhYIb6GlBikkYeuamhps2bZDLz2kmIyTAZofn5LsZon3Brhi4XB3xLWzBssASgsJQoZ15V3vwZgTsrYHnfKq' +
|
|
71
|
+
'kZOt31mCTCZDjx7cyXcJSeIesgFAfCJ3MySpVIoeodwHgrqQni5MO4y2zMbNUbyvyuFzZsLSUvd6JpKw9a49ew12' +
|
|
72
|
+
'OG9STqZhfArLAOe8rfDyv92x7Ck3nPdumm9yu48PrKy4v4gHY07Ivqy6U9yvGCQEBwXythi4cSNbbz18kOgIDCCb' +
|
|
73
|
+
'ztAa+F4FKMCdO7k4dvwkp4ynhweeGc+dxc0F34Evy7JYv4G/eFMoTMrJAEBUsQIRwz3wPwNckObSsoOosJSi+5CW' +
|
|
74
|
+
'dzOtGnNSVIvMVP1fYby9+d+li0vEbwtZVMx/iB4YKKyTqaurN1rJgrmxniScPT9cp7VJwtanYuOQkXldp/V1weSc' +
|
|
75
|
+
'zK3KKliWk6Xz5/Xzhcyy+Y/QqjEnccKMOfH25k/2U1brXqpACl+zJADEnepI0bKPb0Zva4mN+xPXs7gLfgf0D0P3' +
|
|
76
|
+
'bvxHAo9ClHxngLD1wxh3+HQLVEZnAvP4e++W2EgROqArrsU2zT9QDyJ7Ugs55oSrCK6BN19/VfSsW4mU/9lhb2cn' +
|
|
77
|
+
'qg2UlmFZFhs2ReLLL1ZxykXMD8drr79NvC5J2PpGdjZOnIwhXlMITNLJ5NzIQ/c8JbJ8bHhli/r7QXo6vdFh2oMx' +
|
|
78
|
+
'J9ZEuoQcc/Jo5/zm8PPzhZ+frzAK9YDEVop47Nq9Fx+8v4KzXumF56fiw5WfEh/QkoSt123YbPA6MpN7XWpAHUd2' +
|
|
79
|
+
'EKuwlyG0X3Cja6RjTjwr1YKOOSHpPWsq2PH07KWIS1VVFW/Wt7W1NaY/T1bPRBK2rqiowK7de4ltFAqTdTIZyXcQ' +
|
|
80
|
+
'oCDrH1M5OBANJR9BnXxww4vsKe127ha0GuG8ulzHuipjILUgyx2iiMeGTZHN9tV5mAXz5xLVM5GErXfs3EN0Xic0' +
|
|
81
|
+
'JutkAMDiDFm4966TJbr2DgIA2A4PIfoZIcecNKAmqCY3FWhmrvG5nnUDsXF/csp0DA7C0CGDedfiO/DVarVYv4k/' +
|
|
82
|
+
'qiUGJu1kUq9mw6+ErNeKekgw/P09cL092YGmUGNOHkalMp/etOfOXzC2CRSQhbP56pkGDgjjDVv/cewEbt681RrT' +
|
|
83
|
+
'BMMkD34bYFnA5txNYAJ/rdItdyv4TO9j0DEnj6IiaD51LT4Bt0UuK+BCXV+PhMRkbDTSU43SmGPHT+L27TucA/nG' +
|
|
84
|
+
'jhmF9u3b4e7de83eX7woglfP2vXi9ozhwqSdDAAkX8qC71NBKHDgz9zNcyQ7E/G7mocreo45aY6y8nJemagt27F1' +
|
|
85
|
+
'+07BdVPME41Gg02RW/DxRy2PrpFKpZgXPhsrP/msyT2SsHVqWjri/jyjt626YtKvSwCg1WjheuGOYOsJMeakJUoI' +
|
|
86
|
+
'hry7urqIoptivmzdvhM1PE3yw2fPhFze9CFKErZeu854uxjADJwMACSdSYe7Upj+K0HxBajWY8wJF3n5/AWWPj7i' +
|
|
87
|
+
'jd6lmCfl5fexj2dyo5ubK559pnEzNJKwdUlJqdF7/JiFk1GrNfC6pP85hpWaRdYpcXYxAHhTxQGgezey6Bfl8YKk' +
|
|
88
|
+
'YHHB/PBGf582dTJv2Dpq63aj15SZhZMBgORTKXCq1W830ylVgXI9xpzwUVxcwtu394mePZrd9lIeb5JTUnH+wkVO' +
|
|
89
|
+
'mX/1e7JRK5GFC+ZxytfX12Nz5BYhzNMLs3EyqnoNfOPzdP55IcackHDp8hXO+zY2NhgQFia6HRTzg6g95/xwAGRh' +
|
|
90
|
+
'60NHjppEv2WzcTIAkB6dBtt63QqN9B1zQsq589xPIwCYMmWi6HZQzI9DR44iv6CAU2balElwcnLEooUEYWsjH/g2' +
|
|
91
|
+
'YFZOplpZi8BE7i+hOSQscE/PBuGk/HHsOK/M5InPwslJ+PaXFPOmvv7BtEkurK2t8e5bb2D8OO6w9dVr8by7akNh' +
|
|
92
|
+
'Vk4GAK6fSIG1unWZul2zy5Gfr/uYk9aQdSMbySnch8s2NjZYtnSJKPqt5HJ8tfpzXL5wGknXLjb6c+3yWaz5/v9o' +
|
|
93
|
+
'BbYJE7VlO+/AvyUvLeQPWxu4ZwwXZudkKiqVCE4jH4omxJiT1sI3+wgAXn5pIYKDggTX/cN3X2PB/HB07tQRHTr4' +
|
|
94
|
+
'N/oTHBSEmTOmY9bMFwTXSxGGQoUCBw8d1muNgsJC/HrwkEAW6Y/ZORkAuHUyBTKC8SkA0OW2/mNOWsvOXT+hooL7' +
|
|
95
|
+
'/Mfa2hqbNvzIOy+7NbyxfBmmTZ3MK1dbQ9tkmjIk9UxcbI7cKtj4YyEwSydTXFyJzplkTasrog3fQb+qqgrffr+G' +
|
|
96
|
+
'V653ryewJXIDZDLdO9M38MbyZfjgP+/xyqnVakTHnNJbH0U8Ll2+goQE7okTLVGrUiFyyzaBLdIPs3QyAHDvRAqk' +
|
|
97
|
+
'PL04hBhzois//LgOubl3eeXGjR2NA/t2w9nZSSc9Tk6O2LF1M5GDAYCorTtw957uqQAUw7B+k27TBPbv/1XQGetC' +
|
|
98
|
+
'YLZOpqCgDF1yuNsSCjHmRFdqamvx8rLlRK0OnxoyCBfPxWHqlEmQSMi+ErncEkteWoj4y+fxzASy8Rn5BQVY9fl/' +
|
|
99
|
+
'iWQpxmX//l9RUtL6YIUxq61bwmydDAAojqe1+AECioUZc6IPsXGniV6bAMDL0xObN6xBwpXzWPHOmxg8aABcXV3A' +
|
|
100
|
+
'MAwkEgnc3d0Q0qUzxo0djdWff4rUpKv4/NOVxAWXSqUSz784W6d/uBTDU6tStbpa/8zZ80hKbtpU39iYfKsHLu7m' +
|
|
101
|
+
'FqHn7Qpk+Ds0uSeNFWbMib589PEqBAZ2wLMTxhPJd+jgjxXvvNnoGsuyRC0YW6JWpcLciMU6v+dTjMPmqK149ZUl' +
|
|
102
|
+
'kErJWqWu22A6YeuHMeudDPDX+JRHEHLMib5otVrMX/ASfj10ROc19HEwZWXlmDj5efz+B3+SIMW0yM29i6O/HyOW' +
|
|
103
|
+
'/e3oHyJbpBtm72RybuShY151o2t2p4UbcyIEdXX1mBexGBs2RRlUb1JyCoaNGoez52irTXNlA2EHw/UbI3lnbBsL' +
|
|
104
|
+
's3cyAKCO+2fkpmelGsmXhRtzIhRqtRpvvr0Cc+YuEP30X6lU4tPP/othI8ciO9s0dnSGxpQeMvqgUPDneCmVSmzb' +
|
|
105
|
+
'YbrdFtuEk3l4fIrruZuCjjkRml8OHkbvfgPx49r1UCqFbTuhKCrCl19/i159B+CL1V8LmpB169ZtzvuGdGa3b3N3' +
|
|
106
|
+
'StRqtcjN1e3Q/9Zt7s9JkpYgJBHzuZuIA8CuPXuJB8AZA6nc2u5DYxshBF5qgPF3wa2fLkCrMe3HmEqlQnRMLDZH' +
|
|
107
|
+
'bUVJSSmcnZzg5eWp09mLSlWH02fO4ONPv8Cy195AzKk4UWbrpKVnYED/MDg5Nc7nYVkWGZnXsXTZ60SdAYUgPSMD' +
|
|
108
|
+
'HTsGIzAgABaPzI8qL7+Pjz75DNExsTqtnZycirCwfnB3d2v0fbAsi7T0DLzx1gqD5aHY29tj/ZrvYMkxz4tlWSx6' +
|
|
109
|
+
'6RWUlJpm1JABc4Wxd/Y07d9IQhgG6NzdHxnJ3E8iU8XH2xv/6tcXffr0QmBAAPz8fOFgbw8HBwdIJBJUK5VQKpUo' +
|
|
110
|
+
'LFTg3r17SEvPQEJiMi5euiz4johiGixcMI93XnZ0zClMnGK6tWgMmLVtxslQKG0JhmFw+cJpdOoYzCk3aeqLOBkd' +
|
|
111
|
+
'YyCrWg8DZm2bOJOhUNoaQ58awutgsrNzEHMq1jAG6QF1MhSKCbKQZ2okAKxZt4GobMXYUCdDoZgY7du3w5jRIzll' +
|
|
112
|
+
'7t+/j1179hnIIv2gToZCMTEi5oXzlhJs27EL1dXVnDKmAnUyFIoJIZdbYtYM7miRRqPBxs1bDGOQAFAnQ6GYEJMn' +
|
|
113
|
+
'PQd3dzdOmaO/H+NNjjQlqJOhUEyIhRHcA9sA0+wZwwV1MhSKidC3T2/07vUEp0xaeobZFbxSJ0OhmAgkYesf1qwD' +
|
|
114
|
+
'a2bVnzTjl0IxAVxdXZCeEs85vaK4uARdQ3ujVqUyoGX6QTN+KRQTYe6cWbzjcSK3bDMrB9MAdTIUipGRSqUInz2T' +
|
|
115
|
+
'U+bBCFvTGnVCCnUyFIqRGT1qBPz8fDllDh46gnt5xhnvoy/UyVAoRmb0qBG8MmvWmVfY+mGok6FQjAxft72YU7G4' +
|
|
116
|
+
'cvWagawRHrMeiUKhtAW+/3Et7Ozs0KtXz0bXWa0W16/fwOervzKSZcJAQ9gUCkU0GkLY1MlQKBSR0LISALRBLIVC' +
|
|
117
|
+
'EQUWqJIAKDC2IRQKpW3CMEyBBEC6sQ2hUChtFJbJkAA4a2w7KBRKm0QjYWvPS1iGOWxsSygUSpvkz/Ly8nJJVWlB' +
|
|
118
|
+
'KoCLxraGQqG0LRiWjQL+yvhlWeZd45pDoVDaGBkV5YrdwF9Opqq8IJYFjhvXJgqF0oZ4C4AaeKh2SaLRLgVQbiyL' +
|
|
119
|
+
'KBRK24AB9lSWFR5p+PvfTqaioihLwrDT8Jf3oVAolNbCgom3tkTEw9caTZBS1VTnyG3s8gGMA63QplAorYFlrmsk' +
|
|
120
|
+
'0rHlxfklD19u4kgqSws3SRh2HIAygxlHoVDMGhY4LmEtw2pK7zXpW9HsLExVTXWOlaXNAUgkHQEEi24hhUIxVyoB' +
|
|
121
|
+
'rKwqK1yiUt1vtg6S4VvB1tFzuESCTwCECW0dhUIxT9gHhdWRqGdWVlUVFHHJ8jqZBuxcvUMYLTseYAcDCAHgAcBB' +
|
|
122
|
+
'P1MpFIoZoAZQDhY3ASSyDKKtLbRHioqKqkh++P8BysulpNQJzTwAAAAASUVORK5CYII=';
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type CodeProvider } from '../core/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* One message for whatever sends this deployment's mail, written twice: as HTML, and as
|
|
4
|
+
* the plain text a reader without it is shown. Send both, as alternatives of one message.
|
|
5
|
+
*/
|
|
6
|
+
export interface EmailMessage {
|
|
7
|
+
to: string;
|
|
8
|
+
subject: string;
|
|
9
|
+
text: string;
|
|
10
|
+
html: string;
|
|
11
|
+
/**
|
|
12
|
+
* Images the HTML shows, which travel inside the message and are attached inline under
|
|
13
|
+
* the id the HTML names each by, as `cid:<contentId>`. A sender that leaves them out
|
|
14
|
+
* still sends a whole message: the image's place reads "Verily" instead.
|
|
15
|
+
*/
|
|
16
|
+
images: EmailImage[];
|
|
17
|
+
}
|
|
18
|
+
export interface EmailImage {
|
|
19
|
+
contentId: string;
|
|
20
|
+
filename: string;
|
|
21
|
+
contentType: string;
|
|
22
|
+
/** The image's bytes, in base 64. */
|
|
23
|
+
content: string;
|
|
24
|
+
}
|
|
25
|
+
export interface EmailProviderOptions {
|
|
26
|
+
/** Shown as "Verify with …". */
|
|
27
|
+
name?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Hands one message to whatever sends this deployment's mail: an API, a relay, a queue.
|
|
30
|
+
* Rejecting fails the flow. Nothing here retries, since a second message would carry a
|
|
31
|
+
* code the holder was never shown a place to enter.
|
|
32
|
+
*/
|
|
33
|
+
send(message: EmailMessage): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Proves control of a mailbox by sending a code to it and having the holder read it back.
|
|
37
|
+
* No registration with anybody and nothing published: the proof is that the code arrived,
|
|
38
|
+
* so it says somebody could read that mailbox when the link was made, and nothing later.
|
|
39
|
+
* A domain can hand an address to somebody else, which is why the link expires and is
|
|
40
|
+
* renewed like any other.
|
|
41
|
+
*
|
|
42
|
+
* This sends mail to an address a visitor types, so the message carries the code, the
|
|
43
|
+
* site's name and nothing a visitor chose. How often it may be asked to send is the
|
|
44
|
+
* deployment's to limit, like every other request here.
|
|
45
|
+
*/
|
|
46
|
+
export declare function emailProvider(options: EmailProviderOptions): CodeProvider;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { Refused } from '../core/index.js';
|
|
2
|
+
import { emailLogo } from './email-logo.js';
|
|
3
|
+
import { escape } from './escape.js';
|
|
4
|
+
/** The logotype as the message carries it. */
|
|
5
|
+
const logo = {
|
|
6
|
+
contentId: 'verily-logo',
|
|
7
|
+
filename: 'verily.png',
|
|
8
|
+
contentType: 'image/png',
|
|
9
|
+
content: emailLogo,
|
|
10
|
+
};
|
|
11
|
+
/** Atoms and single dots, as an address is written without quoting. */
|
|
12
|
+
const local = /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*$/;
|
|
13
|
+
const domain = /^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$/;
|
|
14
|
+
/**
|
|
15
|
+
* Proves control of a mailbox by sending a code to it and having the holder read it back.
|
|
16
|
+
* No registration with anybody and nothing published: the proof is that the code arrived,
|
|
17
|
+
* so it says somebody could read that mailbox when the link was made, and nothing later.
|
|
18
|
+
* A domain can hand an address to somebody else, which is why the link expires and is
|
|
19
|
+
* renewed like any other.
|
|
20
|
+
*
|
|
21
|
+
* This sends mail to an address a visitor types, so the message carries the code, the
|
|
22
|
+
* site's name and nothing a visitor chose. How often it may be asked to send is the
|
|
23
|
+
* deployment's to limit, like every other request here.
|
|
24
|
+
*/
|
|
25
|
+
export function emailProvider(options) {
|
|
26
|
+
return {
|
|
27
|
+
id: 'email',
|
|
28
|
+
name: options.name ?? 'Email',
|
|
29
|
+
method: 'code',
|
|
30
|
+
field: 'Your email address',
|
|
31
|
+
input: 'email',
|
|
32
|
+
account,
|
|
33
|
+
async deliver({ account, link, code, siteName, expiresAt }) {
|
|
34
|
+
// The name lands in a header, where a line break would start another one.
|
|
35
|
+
const site = siteName.replace(/\s+/g, ' ').trim();
|
|
36
|
+
const words = {
|
|
37
|
+
ask: `Press the button to show ${site} that this address is yours.`,
|
|
38
|
+
button: 'Confirm this address',
|
|
39
|
+
enter: 'Or enter this code on the page that asked for it:',
|
|
40
|
+
lasts: `Both work until ${new Date(expiresAt).toISOString().slice(11, 16)} UTC.`,
|
|
41
|
+
ignore: 'If you did not ask for this, ignore this message. Nothing is linked unless you confirm.',
|
|
42
|
+
};
|
|
43
|
+
await options.send({
|
|
44
|
+
to: account.id,
|
|
45
|
+
subject: `Confirm your address for ${site}`,
|
|
46
|
+
text: [
|
|
47
|
+
`Open this link to show ${site} that this address is yours:`,
|
|
48
|
+
link,
|
|
49
|
+
words.enter,
|
|
50
|
+
code,
|
|
51
|
+
words.lasts,
|
|
52
|
+
words.ignore,
|
|
53
|
+
].join('\n\n'),
|
|
54
|
+
html: letter({ link, code, ...words }),
|
|
55
|
+
images: [logo],
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The message as HTML, in the colours of the pages this library serves. Mail clients are
|
|
62
|
+
* not browsers: the layout is a table and every style is on its element, because that is
|
|
63
|
+
* what all of them keep, and the style block only adds a dark scheme where one is honoured.
|
|
64
|
+
* Nothing is fetched, so there is nothing to block and nothing to tell anyone the message
|
|
65
|
+
* was opened: the logotype is an image the message carries with it, on the plate it sits
|
|
66
|
+
* on everywhere else, and where a client will not show it the word stands in its place.
|
|
67
|
+
*
|
|
68
|
+
* Everything sits on the column's middle, said twice over, as an attribute and as a style,
|
|
69
|
+
* since clients differ on which they honour. The code's cell is padded three pixels more
|
|
70
|
+
* on the left: letter spacing follows every character, the last included, and would
|
|
71
|
+
* otherwise push the code that far off centre.
|
|
72
|
+
*/
|
|
73
|
+
function letter(parts) {
|
|
74
|
+
const sans = "system-ui,-apple-system,'Segoe UI',Roboto,Helvetica,Arial,sans-serif";
|
|
75
|
+
const mono = "ui-monospace,SFMono-Regular,Menlo,Consolas,'Liberation Mono',monospace";
|
|
76
|
+
return `<!doctype html>
|
|
77
|
+
<html lang="en">
|
|
78
|
+
<head>
|
|
79
|
+
<meta charset="utf-8">
|
|
80
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
81
|
+
<meta name="color-scheme" content="light dark">
|
|
82
|
+
<meta name="supported-color-schemes" content="light dark">
|
|
83
|
+
<title>Verily</title>
|
|
84
|
+
<style>
|
|
85
|
+
@media (prefers-color-scheme: dark) {
|
|
86
|
+
.page { background: #161a18 !important; }
|
|
87
|
+
.ink { color: #dde5e0 !important; }
|
|
88
|
+
.muted { color: #96a39b !important; }
|
|
89
|
+
.button { background: #7fc4a2 !important; }
|
|
90
|
+
.button a { color: #10231a !important; }
|
|
91
|
+
.code { background: #1d2220 !important; border-color: #2f3b35 !important; color: #dde5e0 !important; }
|
|
92
|
+
.rule { border-color: #2f3b35 !important; }
|
|
93
|
+
}
|
|
94
|
+
</style>
|
|
95
|
+
</head>
|
|
96
|
+
<body class="page" style="margin:0;padding:0;background:#ffffff;">
|
|
97
|
+
<div style="display:none;max-height:0;overflow:hidden;opacity:0;">${escape(parts.ask)} ${escape(parts.lasts)}</div>
|
|
98
|
+
<table role="presentation" class="page" width="100%" cellpadding="0" cellspacing="0" border="0" style="background:#ffffff;">
|
|
99
|
+
<tr><td align="center" style="padding:40px 16px 48px;">
|
|
100
|
+
<table role="presentation" align="center" width="100%" cellpadding="0" cellspacing="0" border="0" style="max-width:440px;margin:0 auto;">
|
|
101
|
+
<tr><td align="center" style="padding:0 0 28px;">
|
|
102
|
+
<img src="cid:${logo.contentId}" width="94" height="40" alt="Verily" style="display:block;margin:0 auto;border:0;outline:none;font:700 20px/40px ${sans};color:#23312b;">
|
|
103
|
+
</td></tr>
|
|
104
|
+
<tr><td class="ink" align="center" style="padding:0 0 10px;font:650 22px/1.3 ${sans};color:#23312b;text-align:center;">Confirm your address</td></tr>
|
|
105
|
+
<tr><td class="ink" align="center" style="padding:0 0 24px;font:16px/1.6 ${sans};color:#23312b;text-align:center;">${escape(parts.ask)}</td></tr>
|
|
106
|
+
<tr><td align="center" style="padding:0 0 28px;">
|
|
107
|
+
<table role="presentation" align="center" cellpadding="0" cellspacing="0" border="0" style="margin:0 auto;"><tr><td class="button" align="center" style="background:#245f43;border-radius:10px;"><a href="${escape(parts.link)}" style="display:inline-block;padding:13px 26px;font:600 16px/1.2 ${sans};color:#ffffff;text-decoration:none;">${escape(parts.button)}</a></td></tr></table>
|
|
108
|
+
</td></tr>
|
|
109
|
+
<tr><td class="muted" align="center" style="padding:0 0 10px;font:14px/1.5 ${sans};color:#6b786f;text-align:center;">${escape(parts.enter)}</td></tr>
|
|
110
|
+
<tr><td class="code" align="center" style="padding:14px 12px 14px 15px;background:#f7f9f7;border:1px solid #dce2de;border-radius:10px;font:600 22px/1.2 ${mono};letter-spacing:.14em;color:#23312b;text-align:center;">${escape(parts.code)}</td></tr>
|
|
111
|
+
<tr><td class="muted" align="center" style="padding:12px 0 28px;font:14px/1.5 ${sans};color:#6b786f;text-align:center;">${escape(parts.lasts)}</td></tr>
|
|
112
|
+
<tr><td class="rule" style="border-top:1px solid #dce2de;font-size:0;line-height:0;"> </td></tr>
|
|
113
|
+
<tr><td class="muted" align="center" style="padding:18px 0 0;font:13px/1.6 ${sans};color:#6b786f;text-align:center;">${escape(parts.ignore)}</td></tr>
|
|
114
|
+
</table>
|
|
115
|
+
</td></tr>
|
|
116
|
+
</table>
|
|
117
|
+
</body>
|
|
118
|
+
</html>`;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* The mailbox an address names. Lowercased throughout: the part before the @ may be case
|
|
122
|
+
* sensitive by the letter of the standard and is not in any mailbox a person holds, and
|
|
123
|
+
* two spellings of one address must not be two records. Nothing else is folded, because
|
|
124
|
+
* whether a dot or a plus suffix names the same mailbox is each domain's own rule.
|
|
125
|
+
*/
|
|
126
|
+
function account(address) {
|
|
127
|
+
const text = address.trim().toLowerCase();
|
|
128
|
+
const at = text.lastIndexOf('@');
|
|
129
|
+
const name = text.slice(0, at);
|
|
130
|
+
const host = text.slice(at + 1);
|
|
131
|
+
if (at < 1 ||
|
|
132
|
+
text.length > 254 ||
|
|
133
|
+
name.length > 64 ||
|
|
134
|
+
!local.test(name) ||
|
|
135
|
+
!domain.test(host) ||
|
|
136
|
+
// An address literal, or a name nobody could have registered.
|
|
137
|
+
/^[\d.]+$/.test(host))
|
|
138
|
+
throw new Refused('Not an email address this can send to');
|
|
139
|
+
return {
|
|
140
|
+
id: text,
|
|
141
|
+
kind: 'mailbox',
|
|
142
|
+
handle: text,
|
|
143
|
+
profileUrl: `mailto:${encodeURIComponent(name)}@${host}`,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ArtifactProvider } from '../core/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Proves control of a GitHub account by having its holder publish a given line in a public
|
|
4
|
+
* gist. No app registration, no client secret, and no token: the proof is a public document
|
|
5
|
+
* anyone can fetch, so a reader can repeat this check without trusting the backend that ran
|
|
6
|
+
* it. The unauthenticated API allows sixty requests an hour per address, which bounds how
|
|
7
|
+
* often connections using this method can be rechecked.
|
|
8
|
+
*/
|
|
9
|
+
export declare function githubGistProvider(options?: {
|
|
10
|
+
fetch?: typeof fetch;
|
|
11
|
+
}): ArtifactProvider;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { Refused } from '../core/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Proves control of a GitHub account by having its holder publish a given line in a public
|
|
4
|
+
* gist. No app registration, no client secret, and no token: the proof is a public document
|
|
5
|
+
* anyone can fetch, so a reader can repeat this check without trusting the backend that ran
|
|
6
|
+
* it. The unauthenticated API allows sixty requests an hour per address, which bounds how
|
|
7
|
+
* often connections using this method can be rechecked.
|
|
8
|
+
*/
|
|
9
|
+
export function githubGistProvider(options = {}) {
|
|
10
|
+
const request = options.fetch ?? fetch;
|
|
11
|
+
return {
|
|
12
|
+
id: 'github',
|
|
13
|
+
name: 'GitHub',
|
|
14
|
+
method: 'gist',
|
|
15
|
+
artifact: 'location',
|
|
16
|
+
instructions: (expect) => [
|
|
17
|
+
[
|
|
18
|
+
'Publish this line in a public gist at ',
|
|
19
|
+
{ text: 'gist.github.com', href: 'https://gist.github.com/' },
|
|
20
|
+
', then paste the gist address below.',
|
|
21
|
+
],
|
|
22
|
+
{ code: expect },
|
|
23
|
+
],
|
|
24
|
+
async verify({ artifact, expect }) {
|
|
25
|
+
const url = URL.parse(artifact);
|
|
26
|
+
// The holder chooses this url, so nothing outside gist.github.com is ever fetched.
|
|
27
|
+
if (!url || url.protocol !== 'https:' || url.host !== 'gist.github.com')
|
|
28
|
+
throw new Refused('Not a gist address');
|
|
29
|
+
const id = url.pathname.split('/').filter(Boolean).at(-1) ?? '';
|
|
30
|
+
if (!/^[0-9a-f]{20,32}$/.test(id))
|
|
31
|
+
throw new Refused('Not a gist address');
|
|
32
|
+
const response = await request(`https://api.github.com/gists/${id}`, {
|
|
33
|
+
headers: { Accept: 'application/vnd.github+json', 'User-Agent': 'Verily-V0' },
|
|
34
|
+
signal: AbortSignal.timeout(15000),
|
|
35
|
+
});
|
|
36
|
+
const gist = await response.json();
|
|
37
|
+
if (!response.ok || !isRecord(gist))
|
|
38
|
+
throw new Refused('Gist unavailable');
|
|
39
|
+
// A secret gist proves nothing a reader could check for themselves.
|
|
40
|
+
if (gist.public !== true)
|
|
41
|
+
throw new Refused('Gist is not public');
|
|
42
|
+
const owner = gist.owner;
|
|
43
|
+
if (!isRecord(owner) ||
|
|
44
|
+
!Number.isSafeInteger(owner.id) ||
|
|
45
|
+
Number(owner.id) <= 0 ||
|
|
46
|
+
typeof owner.login !== 'string' ||
|
|
47
|
+
!/^[a-zA-Z0-9-]+$/.test(owner.login))
|
|
48
|
+
throw new Refused('Invalid gist owner');
|
|
49
|
+
if (!isRecord(gist.files) || !contains(gist.files, expect))
|
|
50
|
+
throw new Refused('Gist does not contain the expected line');
|
|
51
|
+
return {
|
|
52
|
+
id: String(owner.id),
|
|
53
|
+
handle: owner.login,
|
|
54
|
+
profileUrl: `https://github.com/${owner.login}`,
|
|
55
|
+
};
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
/** A truncated file is not proof: the part that would have matched may be the part omitted. */
|
|
60
|
+
function contains(files, expect) {
|
|
61
|
+
return Object.values(files).some((file) => isRecord(file) &&
|
|
62
|
+
file.truncated !== true &&
|
|
63
|
+
typeof file.content === 'string' &&
|
|
64
|
+
file.content.includes(expect));
|
|
65
|
+
}
|
|
66
|
+
function isRecord(value) {
|
|
67
|
+
return value !== null && typeof value === 'object';
|
|
68
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { RedirectProvider } from '../core/index.js';
|
|
2
|
+
/** GitHub.com OAuth app, PKCE S256, no additional scopes. Tokens are never persisted. */
|
|
3
|
+
export declare function githubProvider(options: {
|
|
4
|
+
clientId: string;
|
|
5
|
+
clientSecret: string;
|
|
6
|
+
fetch?: typeof fetch;
|
|
7
|
+
}): RedirectProvider;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** GitHub.com OAuth app, PKCE S256, no additional scopes. Tokens are never persisted. */
|
|
2
|
+
export function githubProvider(options) {
|
|
3
|
+
if (!options.clientId || !options.clientSecret)
|
|
4
|
+
throw new Error('GitHub credentials required');
|
|
5
|
+
const request = options.fetch ?? fetch;
|
|
6
|
+
return {
|
|
7
|
+
id: 'github',
|
|
8
|
+
name: 'GitHub',
|
|
9
|
+
method: 'oauth',
|
|
10
|
+
authorizationUrl({ state, challenge, redirectUri }) {
|
|
11
|
+
const url = new URL('https://github.com/login/oauth/authorize');
|
|
12
|
+
url.search = new URLSearchParams({
|
|
13
|
+
client_id: options.clientId,
|
|
14
|
+
redirect_uri: redirectUri,
|
|
15
|
+
state,
|
|
16
|
+
code_challenge: challenge,
|
|
17
|
+
code_challenge_method: 'S256',
|
|
18
|
+
scope: '',
|
|
19
|
+
prompt: 'select_account',
|
|
20
|
+
}).toString();
|
|
21
|
+
return url.href;
|
|
22
|
+
},
|
|
23
|
+
async authenticate({ code, verifier, redirectUri }) {
|
|
24
|
+
const response = await request('https://github.com/login/oauth/access_token', {
|
|
25
|
+
method: 'POST',
|
|
26
|
+
headers: { Accept: 'application/json' },
|
|
27
|
+
signal: AbortSignal.timeout(15000),
|
|
28
|
+
body: new URLSearchParams({
|
|
29
|
+
client_id: options.clientId,
|
|
30
|
+
client_secret: options.clientSecret,
|
|
31
|
+
code,
|
|
32
|
+
code_verifier: verifier,
|
|
33
|
+
redirect_uri: redirectUri,
|
|
34
|
+
}),
|
|
35
|
+
});
|
|
36
|
+
const token = await response.json();
|
|
37
|
+
if (!response.ok || !isRecord(token) || typeof token.access_token !== 'string' || token.error)
|
|
38
|
+
throw new Error('Provider authentication failed');
|
|
39
|
+
const identity = await request('https://api.github.com/user', {
|
|
40
|
+
headers: {
|
|
41
|
+
Authorization: `Bearer ${token.access_token}`,
|
|
42
|
+
Accept: 'application/vnd.github+json',
|
|
43
|
+
'User-Agent': 'Verily-V0',
|
|
44
|
+
},
|
|
45
|
+
signal: AbortSignal.timeout(15000),
|
|
46
|
+
});
|
|
47
|
+
const user = await identity.json();
|
|
48
|
+
if (!identity.ok ||
|
|
49
|
+
!isRecord(user) ||
|
|
50
|
+
!Number.isSafeInteger(user.id) ||
|
|
51
|
+
Number(user.id) <= 0 ||
|
|
52
|
+
typeof user.login !== 'string' ||
|
|
53
|
+
!/^[a-zA-Z0-9-]+$/.test(user.login))
|
|
54
|
+
throw new Error('Invalid provider identity');
|
|
55
|
+
return {
|
|
56
|
+
id: String(user.id),
|
|
57
|
+
handle: user.login,
|
|
58
|
+
profileUrl: `https://github.com/${user.login}`,
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function isRecord(value) {
|
|
64
|
+
return typeof value === 'object' && value !== null;
|
|
65
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import { type FlowResult, type LocalAccount, type Visibility } from '../core/index.js';
|
|
3
|
+
import { VerilyService, type ServiceOptions } from './service.js';
|
|
4
|
+
export { VerilyService, Unavailable } from './service.js';
|
|
5
|
+
export { githubProvider } from './github.js';
|
|
6
|
+
export { discordProvider } from './discord.js';
|
|
7
|
+
export { youtubeProvider } from './youtube.js';
|
|
8
|
+
export { githubGistProvider } from './github-gist.js';
|
|
9
|
+
export { linkProvider, githubLinkProvider, type LinkProviderOptions } from './link.js';
|
|
10
|
+
export { pgpProvider } from './pgp.js';
|
|
11
|
+
export { generateSigningKey, signer } from './signing.js';
|
|
12
|
+
export { emailProvider, type EmailImage, type EmailMessage, type EmailProviderOptions, } from './email.js';
|
|
13
|
+
export { resendSender } from './resend.js';
|
|
14
|
+
export type { ServiceOptions } from './service.js';
|
|
15
|
+
export interface ServerOptions extends ServiceOptions {
|
|
16
|
+
/** Resolve identity exclusively from the adopting application's authenticated session. */
|
|
17
|
+
authenticate(request: Request): Promise<LocalAccount | undefined>;
|
|
18
|
+
reportUrl: string;
|
|
19
|
+
/**
|
|
20
|
+
* What to store on a flow when it is created, read from the request that creates it, and
|
|
21
|
+
* handed back to `finish`. Never called for removal from the external side, whatever the
|
|
22
|
+
* browser is signed into, since nothing local answers for that.
|
|
23
|
+
*/
|
|
24
|
+
context?(request: Request): Promise<Record<string, string> | undefined>;
|
|
25
|
+
/**
|
|
26
|
+
* Where to send the holder once a flow has ended, each time its result page is served. It
|
|
27
|
+
* must depend on nothing but what it is given, so a page loaded again after a failure or
|
|
28
|
+
* a lost response sends the holder to the same place. Undefined shows the result page.
|
|
29
|
+
*/
|
|
30
|
+
finish?(ended: Ended): Promise<string | undefined>;
|
|
31
|
+
/**
|
|
32
|
+
* Origins a form here may end up at, beyond this one and the sign-in providers': anywhere
|
|
33
|
+
* `finish` sends a holder, since an approval form's redirect is held to `form-action`.
|
|
34
|
+
*/
|
|
35
|
+
formTargets?: string[];
|
|
36
|
+
/**
|
|
37
|
+
* The provider ids a subject may use, where not every subject gets every one: an
|
|
38
|
+
* instance serving several sites offers each site's subjects what that site chose.
|
|
39
|
+
* Undefined means all of them. Removal from the external side is never narrowed.
|
|
40
|
+
*/
|
|
41
|
+
providersFor?(local: LocalAccount): string[] | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* The visibilities a subject may choose, where a site does not offer both. Undefined
|
|
44
|
+
* means both. With one, the approval page states it and offers no choice, and there is
|
|
45
|
+
* nothing for a visibility flow to change. A record made before the choice was narrowed
|
|
46
|
+
* keeps the visibility it has.
|
|
47
|
+
*/
|
|
48
|
+
visibilityFor?(local: LocalAccount): Visibility[] | undefined;
|
|
49
|
+
}
|
|
50
|
+
/** A flow that has ended, as `finish` is given it: all of it read from the stored flow. */
|
|
51
|
+
export interface Ended {
|
|
52
|
+
/** The flow's id, which names it and is no secret. */
|
|
53
|
+
id: string;
|
|
54
|
+
context?: Record<string, string>;
|
|
55
|
+
local?: LocalAccount;
|
|
56
|
+
result: FlowResult;
|
|
57
|
+
}
|
|
58
|
+
export declare function createVerily(options: ServerOptions): {
|
|
59
|
+
service: VerilyService;
|
|
60
|
+
handle: (request: Request) => Promise<Response>;
|
|
61
|
+
/**
|
|
62
|
+
* The flow binding, for a host that carries it some way other than this handler's own
|
|
63
|
+
* cookie: the cookie's name, to read a binding from a request or a response, and the
|
|
64
|
+
* header that sets it in a browser.
|
|
65
|
+
*/
|
|
66
|
+
flowCookieName: string;
|
|
67
|
+
flowCookie: (binding: string) => string;
|
|
68
|
+
};
|
|
69
|
+
/** Mount on your chosen Node router. Public origin is configured, never inferred from Host. */
|
|
70
|
+
export declare function nodeHandler(handler: (request: Request) => Promise<Response>, origin: string): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|