d7be2e0c by Adam Heath

Initial pass of code.

1 parent 52c693a0
{
"name": "astro-oidc-middleware",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "astro-oidc-middleware",
"dependencies": {
"cors": "^2.8.5",
"express-session": "^1.18.0",
"openid-client": "^5.6.5",
"passport": "^0.7.0",
"read-env": "^2.0.0",
"redirecter": "^0.2.3",
"session-file-store": "^1.5.0"
}
},
"node_modules/@types/camelcase": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@types/camelcase/-/camelcase-5.2.0.tgz",
"integrity": "sha512-zhHaryYYUUsJ1h6Rq4hisPkljY7c2bkC5PFYQbom5fyKloGJEDK+wdsw2L4hnBwXr4plGjW6D/UVJBbNbOzVpQ==",
"deprecated": "This is a stub types definition. camelcase provides its own type definitions, so you do not need this installed.",
"dependencies": {
"camelcase": "*"
}
},
"node_modules/asn1.js": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz",
"integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==",
"dependencies": {
"bn.js": "^4.0.0",
"inherits": "^2.0.1",
"minimalistic-assert": "^1.0.0",
"safer-buffer": "^2.1.0"
}
},
"node_modules/bagpipe": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/bagpipe/-/bagpipe-0.3.5.tgz",
"integrity": "sha512-42sAlmPDKes1nLm/aly+0VdaopSU9br+jkRELedhQxI5uXHgtk47I83Mpmf4zoNTRMASdLFtUkimlu/Z9zQ8+g=="
},
"node_modules/bn.js": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz",
"integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA=="
},
"node_modules/camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"engines": {
"node": ">=6"
}
},
"node_modules/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-signature": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="
},
"node_modules/cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"dependencies": {
"object-assign": "^4",
"vary": "^1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/express-session": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.0.tgz",
"integrity": "sha512-m93QLWr0ju+rOwApSsyso838LQwgfs44QtOP/WBiwtAgPIo/SAh1a5c6nn2BR6mFNZehTpqKDESzP+fRHVbxwQ==",
"dependencies": {
"cookie": "0.6.0",
"cookie-signature": "1.0.7",
"debug": "2.6.9",
"depd": "~2.0.0",
"on-headers": "~1.0.2",
"parseurl": "~1.3.3",
"safe-buffer": "5.2.1",
"uid-safe": "~2.1.5"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/fs-extra": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
"integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^4.0.0",
"universalify": "^0.1.0"
},
"engines": {
"node": ">=6 <7 || >=8"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
},
"node_modules/imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
"engines": {
"node": ">=0.8.19"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
},
"node_modules/jose": {
"version": "4.15.5",
"resolved": "https://registry.npmjs.org/jose/-/jose-4.15.5.tgz",
"integrity": "sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg==",
"funding": {
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/jsonfile": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
"optionalDependencies": {
"graceful-fs": "^4.1.6"
}
},
"node_modules/kruptein": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/kruptein/-/kruptein-2.2.3.tgz",
"integrity": "sha512-BTwprBPTzkFT9oTugxKd3WnWrX630MqUDsnmBuoa98eQs12oD4n4TeI0GbpdGcYn/73Xueg2rfnw+oK4dovnJg==",
"dependencies": {
"asn1.js": "^5.4.1"
},
"engines": {
"node": ">6"
}
},
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/media-types": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/media-types/-/media-types-1.0.3.tgz",
"integrity": "sha512-f7UGJB6IeoN/ko8ZeYmNKEuauh6W3cQzm+n4olfhGc0wAPxhSkKcD/RDpG5u+ypkl9trVxBVQnFijyUzYvfJ/Q==",
"dependencies": {
"negotiator": "~0.2.5"
}
},
"node_modules/minimalistic-assert": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
"integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A=="
},
"node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/negotiator": {
"version": "0.2.8",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.2.8.tgz",
"integrity": "sha512-2iv1EafEsegrlyCHYPn4bMKM0g5wVTNqkdp8AqOggvSLV5znbQfTASWh4eKBqwEcw1awuY8l3U7wX95JSQWFEg==",
"engines": {
"node": "*"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/object-hash": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz",
"integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==",
"engines": {
"node": ">= 6"
}
},
"node_modules/oidc-token-hash": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz",
"integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==",
"engines": {
"node": "^10.13.0 || >=12.0.0"
}
},
"node_modules/on-headers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/openid-client": {
"version": "5.6.5",
"resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.6.5.tgz",
"integrity": "sha512-5P4qO9nGJzB5PI0LFlhj4Dzg3m4odt0qsJTfyEtZyOlkgpILwEioOhVVJOrS1iVH494S4Ee5OCjjg6Bf5WOj3w==",
"dependencies": {
"jose": "^4.15.5",
"lru-cache": "^6.0.0",
"object-hash": "^2.2.0",
"oidc-token-hash": "^5.0.3"
},
"funding": {
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/passport": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/passport/-/passport-0.7.0.tgz",
"integrity": "sha512-cPLl+qZpSc+ireUvt+IzqbED1cHHkDoVYMo30jbJIdOOjQ1MQYZBPiNvmi8UM6lJuOpTPXJGZQk0DtC4y61MYQ==",
"dependencies": {
"passport-strategy": "1.x.x",
"pause": "0.0.1",
"utils-merge": "^1.0.1"
},
"engines": {
"node": ">= 0.4.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/jaredhanson"
}
},
"node_modules/passport-strategy": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
"integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/pause": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz",
"integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg=="
},
"node_modules/random-bytes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
"integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/read-env": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/read-env/-/read-env-2.0.0.tgz",
"integrity": "sha512-gCuo85JH2aWt23HVeFzUUhpLzCRtc9O0jjvpMDG/3vue2aOp7/XllAQjSyL8xGqBifGNYbnWLE0PAqi4AWMBlA==",
"dependencies": {
"@types/camelcase": "5.2.0",
"camelcase": "5.3.1"
}
},
"node_modules/redirecter": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/redirecter/-/redirecter-0.2.3.tgz",
"integrity": "sha512-KxZQ+TR3XhbeaMVBcY59TBSDnyNAjAyl7g2a2vwTgjCaq+ZmhmE4GN1w5JI2uMqaIMdPH4kcQIhXW7OcKqIGXQ==",
"dependencies": {
"media-types": "~1.0.3",
"send-data": "~3.1.2"
}
},
"node_modules/retry": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
"integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
"engines": {
"node": ">= 4"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/send-data": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/send-data/-/send-data-3.1.2.tgz",
"integrity": "sha512-x6nl8B/i2VjihpIgifiXvRaHhHl49ycm78BInqBtCqVh61qPNcWd5zLCDtsHJAFo/gW4nAtljiEIss1fcMw3SQ=="
},
"node_modules/session-file-store": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/session-file-store/-/session-file-store-1.5.0.tgz",
"integrity": "sha512-60IZaJNzyu2tIeHutkYE8RiXVx3KRvacOxfLr2Mj92SIsRIroDsH0IlUUR6fJAjoTW4RQISbaOApa2IZpIwFdQ==",
"dependencies": {
"bagpipe": "^0.3.5",
"fs-extra": "^8.0.1",
"kruptein": "^2.0.4",
"object-assign": "^4.1.1",
"retry": "^0.12.0",
"write-file-atomic": "3.0.3"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
},
"node_modules/typedarray-to-buffer": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
"integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
"dependencies": {
"is-typedarray": "^1.0.0"
}
},
"node_modules/uid-safe": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
"dependencies": {
"random-bytes": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/write-file-atomic": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz",
"integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==",
"dependencies": {
"imurmurhash": "^0.1.4",
"is-typedarray": "^1.0.0",
"signal-exit": "^3.0.2",
"typedarray-to-buffer": "^3.1.5"
}
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
}
}
}
{
"name": "astro-oidc-middleware",
"type": "module",
"exports": {
".": "./src/index.mjs"
},
"dependencies": {
"cors": "^2.8.5",
"express-session": "^1.18.0",
"openid-client": "^5.6.5",
"passport": "^0.7.0",
"read-env": "^2.0.0",
"redirecter": "^0.2.3",
"session-file-store": "^1.5.0"
}
}
import 'dotenv/config'
import oidcMiddleware, { fileStoreOptions } from './oidc-middleware.mjs'
import { oidcParams } from './options.mjs'
const clientLocalsSymbol = Symbol.for("astro.locals")
export default (options) => {
return {
name: 'session',
hooks: {
'astro:server:setup': async ({ server }) => {
server.middlewares.use(await oidcMiddleware({ oidc: oidcParams() }, fileStoreOptions))
server.middlewares.use((req, res, next) => {
req[ clientLocalsSymbol ] = res.locals
next()
})
},
},
}
}
import { EventEmitter } from 'node:events'
import { isEqual } from 'lodash-es'
const READ = Symbol('READ')
const TOUCH = Symbol('TOUCH')
const WRITE = Symbol('WRITE')
export default function (session) {
var Store = session.Store
function FixRacyStore(options = {}) {
var self = this
Store.call(this, options)
this.options = options
this.readEvents = new EventEmitter()
this.writeEvents = new EventEmitter()
this.cache = {}
this.clearCache = {}
}
FixRacyStore.prototype.__proto__ = Store.prototype
FixRacyStore.prototype._setCache = function(sessionId, session, newMode, downgrade) {
const { [ sessionId ]: { timerId, writes, mode = READ } = {} } = this.cache
//console.log(Date.now() + ':setCache', {sessionId, mode, newMode, downgrade})
const newEntry = {timerId, writes, session, mode: (mode === READ || downgrade) ? newMode : mode}
this.cache[ sessionId ] = newEntry
if (mode === newMode && newMode === TOUCH) {
return newEntry
}
clearTimeout(timerId)
if (newEntry.mode !== WRITE) {
newEntry.timerId = setTimeout(() => {
try {
const { [ sessionId ]: { mode } } = this.cache
if (mode === TOUCH) {
//console.log(Date.now() + ':flushing cache', sessionId)
this._flush(sessionId)
} else {
//console.log(Date.now() + ':clearing cache', sessionId)
delete this.cache[ sessionId ]
}
} catch (e) {
console.error(e)
}
}, 1000)
}
return newEntry
}
FixRacyStore.prototype.get = function(sessionId, callback = (err, session) => {}) {
const { [ sessionId ]: { session } = {} } = this.cache
if (session) {
//console.log('read from memory', {sessionId})
callback(null, session)
return
}
this.readEvents.once(sessionId, callback)
if (this.readEvents.listenerCount(sessionId) === 1) {
//console.log('issuing get', {sessionId})
this.options.store.get(sessionId, (err, session) => {
//console.log('got session', {sessionId})
if (!this.readEvents.listenerCount(sessionId)) {
//console.log('race condition fixed!')
}
this._setCache(sessionId, session, READ)
this.readEvents.emit(sessionId, err, session)
})
} else {
//console.log('get already issued', {sessionId})
}
}
FixRacyStore.prototype._flush = function (sessionId, ...callbacks) {
const { session } = this.cache[ sessionId ]
//console.log(Date.now() + ':writing', {sessionId, callbacks: callbacks.length})
this.options.store.set(sessionId, session, (err, session) => {
callbacks.forEach(callback => callback(err, session))
const { [ sessionId ]: cacheEntry } = this.cache
const { writes } = cacheEntry
cacheEntry.writes = undefined
if (writes === undefined || writes.length === 0) {
this._setCache(sessionId, session, READ, true)
} else {
this._flush(sessionId, ...writes)
}
})
}
FixRacyStore.prototype.set = function (sessionId, session, callback) {
if (isEqual(this.cache[ sessionId ], session)) {
//console.log('set:isEqual', {sessionId})
callback(null, session)
return
}
const cacheEntry = this._setCache(sessionId, session, WRITE)
//console.log(Date.now() + ':set', {sessionId, cacheEntry})
this.readEvents.emit(sessionId, null, session)
if (cacheEntry.writes === undefined) {
cacheEntry.writes = []
this._flush(sessionId, callback)
} else {
cacheEntry.writes.push(callback)
}
}
FixRacyStore.prototype.touch = function (sessionId, session, callback) {
//console.log(Date.now() + ':touch', {sessionId})
const cacheEntry = this._setCache(sessionId, session, TOUCH)
callback(null, session)
return
}
FixRacyStore.prototype.destroy = function (sessionId, callback) {
this.readEvents.removeAllListeners(sessionId)
this.options.store.destroy(sessionId, callback)
}
FixRacyStore.prototype.clear = function (callback) {
this.readEvents.removeAllListeners()
this.options.store.clear(callback)
}
FixRacyStore.prototype.length = function (callback) {
this.options.store.length(callback)
}
FixRacyStore.prototype.list = function (callback) {
this.options.store.list(callback)
}
FixRacyStore.prototype.expired = function (sessionId, callback) {
this.options.store.expired(sessionId, callback)
}
return FixRacyStore
}
export { default as FixRacyStore } from './fix-racy-store.mjs'
export { default as oidcMiddleware, fileStoreOptions } from './oidc-middleware.mjs'
export { readEnvOptions, oidcParams } from './options.mjs'
export { default as AstroIntegration } from './astro-integration.mjs'
import express from 'express'
import ExpressSession from 'express-session'
import fileStore from 'session-file-store'
import { merge } from 'lodash-es'
import path from 'path'
import { URL, fileURLToPath, parse, format } from 'url'
import { Issuer, Strategy } from 'openid-client'
import passport from 'passport'
import cors from 'cors'
import qs from 'qs'
import redirect from 'redirecter'
import FixRacyStore from './fix-racy-store.mjs'
const defaultOptions = {
auth: {
prefix: '/',
login: '/login',
loginReturnTo: '/',
callback: '/login/callback',
logout: '/logout',
logoutReturnTo: '/',
refreshToken: '/login/refresh/token',
tokens: '/login/tokens',
},
session: {
secret: 'some-secret',
store: async (options) => new ExpressSession.MemoryStore(),
},
oidc: {
enabled: true,
},
}
export const fileStoreOptions = {
session: {
fileStore: {
path: path.join(path.dirname(fileURLToPath(import.meta.url)), '../node_modules/.sessions'),
},
store: async (options) => {
const racyStoreOptions = {
store: new (fileStore(ExpressSession))(options.session.fileStore),
}
return new (FixRacyStore(ExpressSession))(racyStoreOptions)
},
},
}
const getQuery = (req) => {
const { originalUrl } = req
const url = new URL(originalUrl, 'http://localhost')
const result = {}
for(const [ key, value ] of url.searchParams) {
result[ key ] = value;
}
return result;
}
const parseRawToken = (rawToken, def) => {
return rawToken ? JSON.parse(Buffer.from(rawToken.split('.')[1], 'base64').toString()) : def
}
export default async (...options) => {
options = merge({}, defaultOptions, ...options)
console.log('options', options.oidc)
const app = express.Router()
const sessionStore = await options.session.store(options)
app.use(cors({
allowedHeaders: ['authorization', 'content-type'],
}))
app.use(ExpressSession({
secret: options.session.secret,
resave: false,
saveUninitialized: true,
store: sessionStore,
}))
const fetchOpenidClient = async () => {
const issuer = await Issuer.discover(options.oidc.issuer)
return new issuer.Client({
client_id: options.oidc.clientId,
client_secret: options.oidc.clientSecret,
redirect_uris: [ options.oidc.clientUrlBase + (options.auth.prefix === '/' ? '' : options.auth.prefix) + '/login/callback' ],
token_endpoint_auth_method: 'client_secret_post',
});
}
let oidcClient
const getOpenidClient = async () => {
if (!oidcClient) {
oidcClient = await fetchOpenidClient()
setTimeout(() => oidcClient = null, 60000)
}
return oidcClient
}
const createPassportUser = (tokenSet, userInfo) => ({ claims: tokenSet.claims(), userInfo, tokenSet })
const refreshToken = async (req, force = false) => {
const { session } = req
const { passport: { user: { tokenSet } = {} } = {} } = session
try {
if (tokenSet) {
const now = Date.now() / 1000
const expires_at = tokenSet.expires_at
const { exp } = parseRawToken(tokenSet.refresh_token)
//console.log('checking refresh', {now, expires_at, exp, force})
if (force || now + 60 > expires_at) {
//console.log('trying for token refresh', {refresh_token: tokenSet.refresh_token})
const oidcClient = await getOpenidClient()
const newTokenSet = await oidcClient.refresh(tokenSet.refresh_token)
const { exp: newExp } = parseRawToken(newTokenSet.refresh_token)
//console.log('new refresh_token', {newTokenSet, newExp})
if (newTokenSet) {
const newUserInfo = await oidcClient.userinfo(newTokenSet.access_token)
delete session.passport
session.passport = { user: createPassportUser(newTokenSet, newUserInfo) }
}
}
return session.passport
}
} catch (e) {
console.error(e)
await new Promise((resolve, reject) => {
req.logout(err => {
if (err) {
reject(err)
} else {
resolve()
}
})
})
}
}
if (options.oidc.enabled) {
const auth = express.Router()
if (options.auth.prefix !== '/') {
app.use(options.auth.prefix, auth)
} else {
app.use(auth)
}
auth.use(passport.initialize())
auth.use(passport.session())
passport.use('oidc', new Strategy({ client: await getOpenidClient()}, (tokenSet, userInfo, done) => {
done(null, createPassportUser(tokenSet, userInfo))
}))
passport.serializeUser((user, done) => {
//console.log('serializeUser', user)
done(null, user)
})
passport.deserializeUser((user, done) => {
//console.log('deserializeUser', user)
done(null, user)
})
auth.get(options.auth.login, (req, res, next) => {
const { returnTo = options.auth.loginReturnTo } = getQuery(req)
const state = Buffer.from(JSON.stringify({ returnTo })).toString('base64')
passport.authenticate('oidc', { state })(req, res, next)
})
auth.get(options.auth.callback, passport.authenticate('oidc', { failureRedirect: '/' }), (req, res) => {
try {
const { state } = getQuery(req)
const { returnTo } = state ? JSON.parse(Buffer.from(state, 'base64').toString()) : {}
if (typeof returnTo === 'string' && returnTo.startsWith('/')) {
return redirect(req, res, returnTo)
}
} catch (e) {
console.error(e)
}
console.log('callback:redirect to /')
redirect(req, res, '/')
}, (err, req, res, next) => {
if (err) {
console.error('callback error', err)
return redirect(req, res, '/')
}
next()
})
auth.get(options.auth.logout, (req, res, next) => {
const { session } = req
const { passport: { user: { tokenSet } = {} } = {} } = session
const { returnTo = options.auth.logoutReturnTo } = getQuery(req)
req.logout(async err => {
if (!tokenSet) {
return redirect(req, res, returnTo)
}
const oidcClient = await getOpenidClient()
const originalUrl = `http${req.socket.encrypted ? 's' : ''}://${req.headers.host}${req.originalUrl}`
const returnToURL = new URL(returnTo, originalUrl)
const target = oidcClient.endSessionUrl({ id_token_hint: tokenSet.id_token, post_logout_redirect_uri: returnToURL.toString() })
return redirect(req, res, target)
})
})
auth.get(options.auth.refreshToken, async (req, res, next) => {
await refreshToken(req, true)
const { session } = req
const { passport = {} } = session
const { user: { tokenSet: { access_token, expires_at } = {} } = {} } = passport
res.end(JSON.stringify({ access_token, expires_at }))
})
auth.get(options.auth.tokens, async (req, res, next) => {
const passport = await refreshToken(req, true)
if (passport) {
const { user: { tokenSet } } = passport
const { access_token, expires_at, token_type, id_token } = tokenSet
res.writeHead(200)
res.end(JSON.stringify({ access_token, expires_at, token_type, id_token }))
} else {
res.writeHead(401)
res.end('')
}
})
}
app.use(async (req, res, next) => {
await refreshToken(req, false)
const { session } = req
const { passport: { user: { tokenSet } = {} } = {} } = session
res.locals = Object.assign(res.locals || {}, {
session,
async refreshToken() {
return refreshToken(req, true)
},
})
await next()
})
return app
}
import { default as _ } from 'lodash'
import { readEnv } from 'read-env'
export const readEnvOptions = {
source: process.env,
separator: '__',
format: 'camelcase',
sanitize: {
object: false,
array: false,
bool: true,
'int': true,
'float': true,
},
}
export const oidcParams = () => _.pickBy(_.merge({
//bearerOnly: true,
}, readEnv('OIDC', readEnvOptions)), (value, key) => {
return !_.startsWith(key, 'http')
})