git.iko.soy/rowtype-yoga/psfp
on master help sign in
Add some form of auth
commitf8046d6e24e2d945dcea9b62fc008e404d32b6ce authorMark Eibes <mark.eibes@gmail.com> dateSun 9 Aug 2020, 18:58 (6 years ago) parent2d91747
12 files changed+160−135
server/.gitignore+2−1 server/.vscode/settings.json+1−1 server/access.log+1 server/src/Auth/Handler.purs+46 server/src/Auth/Types.purs+7 server/src/JobQueue.purs+25−30 server/src/JobQueueSpec.purs+10−14 server/src/Main.purs+43−57 server/src/Middleware/JsonBodyParser.purs+1−2 server/src/Node/OS.purs+1−1 server/src/Playground/Playground.purs+7−8 server/src/PscIdeClient.purs+16−21
server/.gitignore+2−1
@@ -1,3 +1,4 @@
1+.authorized-keys
12/bower_components/
23/node_modules/
34/.pulp-cache/
@@ -8,4 +9,4 @@
89/.purs*
910/.psa*
1011/.spago/
11−.DS_Store
12+.DS_Store
server/.vscode/settings.json+1−1
@@ -1,5 +1,5 @@
11{
2− "editor.formatOnSave": false,
2+ "editor.formatOnSave": true,
33 "regreplace.commands": [
44 {
55 "name": "replace double colon with unicode version",
server/access.lognew file+1
@@ -0,0 +1 @@
1+Sun, 09 Aug 2020 16:43:40 GMT: Denied request without token2020-08-09T16:53:17.487Z: Denied request without token2020-08-09T16:56:46.287Z: Access granted to LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tCgpoUUlNQXhUMDlUUngzbFo2QVEvL2RzNG5QVW5KMk1YT0pXQlVhNVUvS0drMjJnRjQ3SkxBSTM1VzhkN1NWWndvCkhhN2phZHZVM1BaNEFuaktVQXhUdysxNURYUWM0RDJycEx1Q3gvSzNGNXN2YUN5djY3UlhiTm5YOVJiRGo3a1cKNm5rRVg5SmVzdFRZbU9oQnB4MjRqQXVHSnV5bmFGUlhnNnJscG80dlBBVTYvZGw2d1hnQlBRNXFvZnJVVWlZcwo5T1NhS051VWdvRzZOMVVWSUYvS25Ya2M5UUpYbG1xSE1JMGovWE1FM01xZ2xuejY5ZUlCVjZqUVRFMldENW1tCllQQmpSdSs2eUxJVTZUUXNzZUZPSmJNY0thd0l6eEoxTlNkOVU2L25Ba3VZTWtzTGhlSllmd2xhQkZaL0ZweksKS3hoM0xQTVZRTFhEVjIzODlIclcxS3lTTitFdUMyVWJ3cllYTWttVHY4NUhUbkhoN3RvWG0xWkRSTTRlODE4egp5dnRnRmNPRFBtUS84ekwyMWpPdzRrei9RNDBNUGd4OW1vMGw0K3BmQzZTRjF6MUdiMER0OWtWT2VkdFkwdlVZCmJJZWVoV1BWSldnOThHRjdXOTFrcTFwQjN3b2tqdjZWa1ZHM0syMnE1QUhudnVsTjRITE92Y0djV1FLOGFwZVMKK3Z2UDVBdjJBM3JHSjBZSUdmL05ycDRaTFpHMHBYYlI4UVdNS2RkdUVVM1pYdFlxV0dTWEx0MlpyKzdNakFFdwpyQ2VjaVcraHNwMzArcUpJcGp4VUpBNXVCdFFyMHBDMGtrTkRITEdJSEpmcG5WRUU2MkJMSGFjczVVRkFsOFg0CmdhanV2MUlyQ0lMVFQ3cVZSczB6OEJaUE1uemd5SVJtYmF6K05PbjlhaGFTOVhFUWE3b0JHVDRwV2NUVHNhelMKUXdHMVdRdjNMMG54bjBMcmpoZGpPSnE1a2QxcDdpQlkwd0trRHZlNU1aUUxHK09FZ0IyYkhNZlloYUZzL21pMQppMnNndHRTYTByMWNTaGl3M0NPWVpSRmp5V3M9Cj1wWHlRCi0tLS0tRU5EIFBHUCBNRVNTQUdFLS0tLS0K2020-08-09T16:57:03.649Z: Access granted to LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tCgpoUUlNQXhUMDlUUngzbFo2QVEvL2RzNG5QVW5KMk1YT0pXQlVhNVUvS0drMjJnRjQ3SkxBSTM1VzhkN1NWWndvCkhhN2phZHZVM1BaNEFuaktVQXhUdysxNURYUWM0RDJycEx1Q3gvSzNGNXN2YUN5djY3UlhiTm5YOVJiRGo3a1cKNm5rRVg5SmVzdFRZbU9oQnB4MjRqQXVHSnV5bmFGUlhnNnJscG80dlBBVTYvZGw2d1hnQlBRNXFvZnJVVWlZcwo5T1NhS051VWdvRzZOMVVWSUYvS25Ya2M5UUpYbG1xSE1JMGovWE1FM01xZ2xuejY5ZUlCVjZqUVRFMldENW1tCllQQmpSdSs2eUxJVTZUUXNzZUZPSmJNY0thd0l6eEoxTlNkOVU2L25Ba3VZTWtzTGhlSllmd2xhQkZaL0ZweksKS3hoM0xQTVZRTFhEVjIzODlIclcxS3lTTitFdUMyVWJ3cllYTWttVHY4NUhUbkhoN3RvWG0xWkRSTTRlODE4egp5dnRnRmNPRFBtUS84ekwyMWpPdzRrei9RNDBNUGd4OW1vMGw0K3BmQzZTRjF6MUdiMER0OWtWT2VkdFkwdlVZCmJJZWVoV1BWSldnOThHRjdXOTFrcTFwQjN3b2tqdjZWa1ZHM0syMnE1QUhudnVsTjRITE92Y0djV1FLOGFwZVMKK3Z2UDVBdjJBM3JHSjBZSUdmL05ycDRaTFpHMHBYYlI4UVdNS2RkdUVVM1pYdFlxV0dTWEx0MlpyKzdNakFFdwpyQ2VjaVcraHNwMzArcUpJcGp4VUpBNXVCdFFyMHBDMGtrTkRITEdJSEpmcG5WRUU2MkJMSGFjczVVRkFsOFg0CmdhanV2MUlyQ0lMVFQ3cVZSczB6OEJaUE1uemd5SVJtYmF6K05PbjlhaGFTOVhFUWE3b0JHVDRwV2NUVHNhelMKUXdHMVdRdjNMMG54bjBMcmpoZGpPSnE1a2QxcDdpQlkwd0trRHZlNU1aUUxHK09FZ0IyYkhNZlloYUZzL21pMQppMnNndHRTYTByMWNTaGl3M0NPWVpSRmp5V3M9Cj1wWHlRCi0tLS0tRU5EIFBHUCBNRVNTQUdFLS0tLS0K2020-08-09T16:57:04.280Z: Access granted to LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tCgpoUUlNQXhUMDlUUngzbFo2QVEvL2RzNG5QVW5KMk1YT0pXQlVhNVUvS0drMjJnRjQ3SkxBSTM1VzhkN1NWWndvCkhhN2phZHZVM1BaNEFuaktVQXhUdysxNURYUWM0RDJycEx1Q3gvSzNGNXN2YUN5djY3UlhiTm5YOVJiRGo3a1cKNm5rRVg5SmVzdFRZbU9oQnB4MjRqQXVHSnV5bmFGUlhnNnJscG80dlBBVTYvZGw2d1hnQlBRNXFvZnJVVWlZcwo5T1NhS051VWdvRzZOMVVWSUYvS25Ya2M5UUpYbG1xSE1JMGovWE1FM01xZ2xuejY5ZUlCVjZqUVRFMldENW1tCllQQmpSdSs2eUxJVTZUUXNzZUZPSmJNY0thd0l6eEoxTlNkOVU2L25Ba3VZTWtzTGhlSllmd2xhQkZaL0ZweksKS3hoM0xQTVZRTFhEVjIzODlIclcxS3lTTitFdUMyVWJ3cllYTWttVHY4NUhUbkhoN3RvWG0xWkRSTTRlODE4egp5dnRnRmNPRFBtUS84ekwyMWpPdzRrei9RNDBNUGd4OW1vMGw0K3BmQzZTRjF6MUdiMER0OWtWT2VkdFkwdlVZCmJJZWVoV1BWSldnOThHRjdXOTFrcTFwQjN3b2tqdjZWa1ZHM0syMnE1QUhudnVsTjRITE92Y0djV1FLOGFwZVMKK3Z2UDVBdjJBM3JHSjBZSUdmL05ycDRaTFpHMHBYYlI4UVdNS2RkdUVVM1pYdFlxV0dTWEx0MlpyKzdNakFFdwpyQ2VjaVcraHNwMzArcUpJcGp4VUpBNXVCdFFyMHBDMGtrTkRITEdJSEpmcG5WRUU2MkJMSGFjczVVRkFsOFg0CmdhanV2MUlyQ0lMVFQ3cVZSczB6OEJaUE1uemd5SVJtYmF6K05PbjlhaGFTOVhFUWE3b0JHVDRwV2NUVHNhelMKUXdHMVdRdjNMMG54bjBMcmpoZGpPSnE1a2QxcDdpQlkwd0trRHZlNU1aUUxHK09FZ0IyYkhNZlloYUZzL21pMQppMnNndHRTYTByMWNTaGl3M0NPWVpSRmp5V3M9Cj1wWHlRCi0tLS0tRU5EIFBHUCBNRVNTQUdFLS0tLS0K2020-08-09T16:57:04.467Z: Access granted to LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tCgpoUUlNQXhUMDlUUngzbFo2QVEvL2RzNG5QVW5KMk1YT0pXQlVhNVUvS0drMjJnRjQ3SkxBSTM1VzhkN1NWWndvCkhhN2phZHZVM1BaNEFuaktVQXhUdysxNURYUWM0RDJycEx1Q3gvSzNGNXN2YUN5djY3UlhiTm5YOVJiRGo3a1cKNm5rRVg5SmVzdFRZbU9oQnB4MjRqQXVHSnV5bmFGUlhnNnJscG80dlBBVTYvZGw2d1hnQlBRNXFvZnJVVWlZcwo5T1NhS051VWdvRzZOMVVWSUYvS25Ya2M5UUpYbG1xSE1JMGovWE1FM01xZ2xuejY5ZUlCVjZqUVRFMldENW1tCllQQmpSdSs2eUxJVTZUUXNzZUZPSmJNY0thd0l6eEoxTlNkOVU2L25Ba3VZTWtzTGhlSllmd2xhQkZaL0ZweksKS3hoM0xQTVZRTFhEVjIzODlIclcxS3lTTitFdUMyVWJ3cllYTWttVHY4NUhUbkhoN3RvWG0xWkRSTTRlODE4egp5dnRnRmNPRFBtUS84ekwyMWpPdzRrei9RNDBNUGd4OW1vMGw0K3BmQzZTRjF6MUdiMER0OWtWT2VkdFkwdlVZCmJJZWVoV1BWSldnOThHRjdXOTFrcTFwQjN3b2tqdjZWa1ZHM0syMnE1QUhudnVsTjRITE92Y0djV1FLOGFwZVMKK3Z2UDVBdjJBM3JHSjBZSUdmL05ycDRaTFpHMHBYYlI4UVdNS2RkdUVVM1pYdFlxV0dTWEx0MlpyKzdNakFFdwpyQ2VjaVcraHNwMzArcUpJcGp4VUpBNXVCdFFyMHBDMGtrTkRITEdJSEpmcG5WRUU2MkJMSGFjczVVRkFsOFg0CmdhanV2MUlyQ0lMVFQ3cVZSczB6OEJaUE1uemd5SVJtYmF6K05PbjlhaGFTOVhFUWE3b0JHVDRwV2NUVHNhelMKUXdHMVdRdjNMMG54bjBMcmpoZGpPSnE1a2QxcDdpQlkwd0trRHZlNU1aUUxHK09FZ0IyYkhNZlloYUZzL21pMQppMnNndHRTYTByMWNTaGl3M0NPWVpSRmp5V3M9Cj1wWHlRCi0tLS0tRU5EIFBHUCBNRVNTQUdFLS0tLS0K2020-08-09T16:57:04.674Z: Access granted to LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tCgpoUUlNQXhUMDlUUngzbFo2QVEvL2RzNG5QVW5KMk1YT0pXQlVhNVUvS0drMjJnRjQ3SkxBSTM1VzhkN1NWWndvCkhhN2phZHZVM1BaNEFuaktVQXhUdysxNURYUWM0RDJycEx1Q3gvSzNGNXN2YUN5djY3UlhiTm5YOVJiRGo3a1cKNm5rRVg5SmVzdFRZbU9oQnB4MjRqQXVHSnV5bmFGUlhnNnJscG80dlBBVTYvZGw2d1hnQlBRNXFvZnJVVWlZcwo5T1NhS051VWdvRzZOMVVWSUYvS25Ya2M5UUpYbG1xSE1JMGovWE1FM01xZ2xuejY5ZUlCVjZqUVRFMldENW1tCllQQmpSdSs2eUxJVTZUUXNzZUZPSmJNY0thd0l6eEoxTlNkOVU2L25Ba3VZTWtzTGhlSllmd2xhQkZaL0ZweksKS3hoM0xQTVZRTFhEVjIzODlIclcxS3lTTitFdUMyVWJ3cllYTWttVHY4NUhUbkhoN3RvWG0xWkRSTTRlODE4egp5dnRnRmNPRFBtUS84ekwyMWpPdzRrei9RNDBNUGd4OW1vMGw0K3BmQzZTRjF6MUdiMER0OWtWT2VkdFkwdlVZCmJJZWVoV1BWSldnOThHRjdXOTFrcTFwQjN3b2tqdjZWa1ZHM0syMnE1QUhudnVsTjRITE92Y0djV1FLOGFwZVMKK3Z2UDVBdjJBM3JHSjBZSUdmL05ycDRaTFpHMHBYYlI4UVdNS2RkdUVVM1pYdFlxV0dTWEx0MlpyKzdNakFFdwpyQ2VjaVcraHNwMzArcUpJcGp4VUpBNXVCdFFyMHBDMGtrTkRITEdJSEpmcG5WRUU2MkJMSGFjczVVRkFsOFg0CmdhanV2MUlyQ0lMVFQ3cVZSczB6OEJaUE1uemd5SVJtYmF6K05PbjlhaGFTOVhFUWE3b0JHVDRwV2NUVHNhelMKUXdHMVdRdjNMMG54bjBMcmpoZGpPSnE1a2QxcDdpQlkwd0trRHZlNU1aUUxHK09FZ0IyYkhNZlloYUZzL21pMQppMnNndHRTYTByMWNTaGl3M0NPWVpSRmp5V3M9Cj1wWHlRCi0tLS0tRU5EIFBHUCBNRVNTQUdFLS0tLS0K2020-08-09T16:57:04.851Z: Access granted to LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tCgpoUUlNQXhUMDlUUngzbFo2QVEvL2RzNG5QVW5KMk1YT0pXQlVhNVUvS0drMjJnRjQ3SkxBSTM1VzhkN1NWWndvCkhhN2phZHZVM1BaNEFuaktVQXhUdysxNURYUWM0RDJycEx1Q3gvSzNGNXN2YUN5djY3UlhiTm5YOVJiRGo3a1cKNm5rRVg5SmVzdFRZbU9oQnB4MjRqQXVHSnV5bmFGUlhnNnJscG80dlBBVTYvZGw2d1hnQlBRNXFvZnJVVWlZcwo5T1NhS051VWdvRzZOMVVWSUYvS25Ya2M5UUpYbG1xSE1JMGovWE1FM01xZ2xuejY5ZUlCVjZqUVRFMldENW1tCllQQmpSdSs2eUxJVTZUUXNzZUZPSmJNY0thd0l6eEoxTlNkOVU2L25Ba3VZTWtzTGhlSllmd2xhQkZaL0ZweksKS3hoM0xQTVZRTFhEVjIzODlIclcxS3lTTitFdUMyVWJ3cllYTWttVHY4NUhUbkhoN3RvWG0xWkRSTTRlODE4egp5dnRnRmNPRFBtUS84ekwyMWpPdzRrei9RNDBNUGd4OW1vMGw0K3BmQzZTRjF6MUdiMER0OWtWT2VkdFkwdlVZCmJJZWVoV1BWSldnOThHRjdXOTFrcTFwQjN3b2tqdjZWa1ZHM0syMnE1QUhudnVsTjRITE92Y0djV1FLOGFwZVMKK3Z2UDVBdjJBM3JHSjBZSUdmL05ycDRaTFpHMHBYYlI4UVdNS2RkdUVVM1pYdFlxV0dTWEx0MlpyKzdNakFFdwpyQ2VjaVcraHNwMzArcUpJcGp4VUpBNXVCdFFyMHBDMGtrTkRITEdJSEpmcG5WRUU2MkJMSGFjczVVRkFsOFg0CmdhanV2MUlyQ0lMVFQ3cVZSczB6OEJaUE1uemd5SVJtYmF6K05PbjlhaGFTOVhFUWE3b0JHVDRwV2NUVHNhelMKUXdHMVdRdjNMMG54bjBMcmpoZGpPSnE1a2QxcDdpQlkwd0trRHZlNU1aUUxHK09FZ0IyYkhNZlloYUZzL21pMQppMnNndHRTYTByMWNTaGl3M0NPWVpSRmp5V3M9Cj1wWHlRCi0tLS0tRU5EIFBHUCBNRVNTQUdFLS0tLS0K2020-08-09T16:57:05.017Z: Access granted to LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tCgpoUUlNQXhUMDlUUngzbFo2QVEvL2RzNG5QVW5KMk1YT0pXQlVhNVUvS0drMjJnRjQ3SkxBSTM1VzhkN1NWWndvCkhhN2phZHZVM1BaNEFuaktVQXhUdysxNURYUWM0RDJycEx1Q3gvSzNGNXN2YUN5djY3UlhiTm5YOVJiRGo3a1cKNm5rRVg5SmVzdFRZbU9oQnB4MjRqQXVHSnV5bmFGUlhnNnJscG80dlBBVTYvZGw2d1hnQlBRNXFvZnJVVWlZcwo5T1NhS051VWdvRzZOMVVWSUYvS25Ya2M5UUpYbG1xSE1JMGovWE1FM01xZ2xuejY5ZUlCVjZqUVRFMldENW1tCllQQmpSdSs2eUxJVTZUUXNzZUZPSmJNY0thd0l6eEoxTlNkOVU2L25Ba3VZTWtzTGhlSllmd2xhQkZaL0ZweksKS3hoM0xQTVZRTFhEVjIzODlIclcxS3lTTitFdUMyVWJ3cllYTWttVHY4NUhUbkhoN3RvWG0xWkRSTTRlODE4egp5dnRnRmNPRFBtUS84ekwyMWpPdzRrei9RNDBNUGd4OW1vMGw0K3BmQzZTRjF6MUdiMER0OWtWT2VkdFkwdlVZCmJJZWVoV1BWSldnOThHRjdXOTFrcTFwQjN3b2tqdjZWa1ZHM0syMnE1QUhudnVsTjRITE92Y0djV1FLOGFwZVMKK3Z2UDVBdjJBM3JHSjBZSUdmL05ycDRaTFpHMHBYYlI4UVdNS2RkdUVVM1pYdFlxV0dTWEx0MlpyKzdNakFFdwpyQ2VjaVcraHNwMzArcUpJcGp4VUpBNXVCdFFyMHBDMGtrTkRITEdJSEpmcG5WRUU2MkJMSGFjczVVRkFsOFg0CmdhanV2MUlyQ0lMVFQ3cVZSczB6OEJaUE1uemd5SVJtYmF6K05PbjlhaGFTOVhFUWE3b0JHVDRwV2NUVHNhelMKUXdHMVdRdjNMMG54bjBMcmpoZGpPSnE1a2QxcDdpQlkwd0trRHZlNU1aUUxHK09FZ0IyYkhNZlloYUZzL21pMQppMnNndHRTYTByMWNTaGl3M0NPWVpSRmp5V3M9Cj1wWHlRCi0tLS0tRU5EIFBHUCBNRVNTQUdFLS0tLS0K2020-08-09T16:57:05.198Z: Access granted to LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tCgpoUUlNQXhUMDlUUngzbFo2QVEvL2RzNG5QVW5KMk1YT0pXQlVhNVUvS0drMjJnRjQ3SkxBSTM1VzhkN1NWWndvCkhhN2phZHZVM1BaNEFuaktVQXhUdysxNURYUWM0RDJycEx1Q3gvSzNGNXN2YUN5djY3UlhiTm5YOVJiRGo3a1cKNm5rRVg5SmVzdFRZbU9oQnB4MjRqQXVHSnV5bmFGUlhnNnJscG80dlBBVTYvZGw2d1hnQlBRNXFvZnJVVWlZcwo5T1NhS051VWdvRzZOMVVWSUYvS25Ya2M5UUpYbG1xSE1JMGovWE1FM01xZ2xuejY5ZUlCVjZqUVRFMldENW1tCllQQmpSdSs2eUxJVTZUUXNzZUZPSmJNY0thd0l6eEoxTlNkOVU2L25Ba3VZTWtzTGhlSllmd2xhQkZaL0ZweksKS3hoM0xQTVZRTFhEVjIzODlIclcxS3lTTitFdUMyVWJ3cllYTWttVHY4NUhUbkhoN3RvWG0xWkRSTTRlODE4egp5dnRnRmNPRFBtUS84ekwyMWpPdzRrei9RNDBNUGd4OW1vMGw0K3BmQzZTRjF6MUdiMER0OWtWT2VkdFkwdlVZCmJJZWVoV1BWSldnOThHRjdXOTFrcTFwQjN3b2tqdjZWa1ZHM0syMnE1QUhudnVsTjRITE92Y0djV1FLOGFwZVMKK3Z2UDVBdjJBM3JHSjBZSUdmL05ycDRaTFpHMHBYYlI4UVdNS2RkdUVVM1pYdFlxV0dTWEx0MlpyKzdNakFFdwpyQ2VjaVcraHNwMzArcUpJcGp4VUpBNXVCdFFyMHBDMGtrTkRITEdJSEpmcG5WRUU2MkJMSGFjczVVRkFsOFg0CmdhanV2MUlyQ0lMVFQ3cVZSczB6OEJaUE1uemd5SVJtYmF6K05PbjlhaGFTOVhFUWE3b0JHVDRwV2NUVHNhelMKUXdHMVdRdjNMMG54bjBMcmpoZGpPSnE1a2QxcDdpQlkwd0trRHZlNU1aUUxHK09FZ0IyYkhNZlloYUZzL21pMQppMnNndHRTYTByMWNTaGl3M0NPWVpSRmp5V3M9Cj1wWHlRCi0tLS0tRU5EIFBHUCBNRVNTQUdFLS0tLS0K2020-08-09T16:57:05.366Z: Access granted to LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tCgpoUUlNQXhUMDlUUngzbFo2QVEvL2RzNG5QVW5KMk1YT0pXQlVhNVUvS0drMjJnRjQ3SkxBSTM1VzhkN1NWWndvCkhhN2phZHZVM1BaNEFuaktVQXhUdysxNURYUWM0RDJycEx1Q3gvSzNGNXN2YUN5djY3UlhiTm5YOVJiRGo3a1cKNm5rRVg5SmVzdFRZbU9oQnB4MjRqQXVHSnV5bmFGUlhnNnJscG80dlBBVTYvZGw2d1hnQlBRNXFvZnJVVWlZcwo5T1NhS051VWdvRzZOMVVWSUYvS25Ya2M5UUpYbG1xSE1JMGovWE1FM01xZ2xuejY5ZUlCVjZqUVRFMldENW1tCllQQmpSdSs2eUxJVTZUUXNzZUZPSmJNY0thd0l6eEoxTlNkOVU2L25Ba3VZTWtzTGhlSllmd2xhQkZaL0ZweksKS3hoM0xQTVZRTFhEVjIzODlIclcxS3lTTitFdUMyVWJ3cllYTWttVHY4NUhUbkhoN3RvWG0xWkRSTTRlODE4egp5dnRnRmNPRFBtUS84ekwyMWpPdzRrei9RNDBNUGd4OW1vMGw0K3BmQzZTRjF6MUdiMER0OWtWT2VkdFkwdlVZCmJJZWVoV1BWSldnOThHRjdXOTFrcTFwQjN3b2tqdjZWa1ZHM0syMnE1QUhudnVsTjRITE92Y0djV1FLOGFwZVMKK3Z2UDVBdjJBM3JHSjBZSUdmL05ycDRaTFpHMHBYYlI4UVdNS2RkdUVVM1pYdFlxV0dTWEx0MlpyKzdNakFFdwpyQ2VjaVcraHNwMzArcUpJcGp4VUpBNXVCdFFyMHBDMGtrTkRITEdJSEpmcG5WRUU2MkJMSGFjczVVRkFsOFg0CmdhanV2MUlyQ0lMVFQ3cVZSczB6OEJaUE1uemd5SVJtYmF6K05PbjlhaGFTOVhFUWE3b0JHVDRwV2NUVHNhelMKUXdHMVdRdjNMMG54bjBMcmpoZGpPSnE1a2QxcDdpQlkwd0trRHZlNU1aUUxHK09FZ0IyYkhNZlloYUZzL21pMQppMnNndHRTYTByMWNTaGl3M0NPWVpSRmp5V3M9Cj1wWHlRCi0tLS0tRU5EIFBHUCBNRVNTQUdFLS0tLS0K2020-08-09T16:57:05.549Z: Access granted to LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tCgpoUUlNQXhUMDlUUngzbFo2QVEvL2RzNG5QVW5KMk1YT0pXQlVhNVUvS0drMjJnRjQ3SkxBSTM1VzhkN1NWWndvCkhhN2phZHZVM1BaNEFuaktVQXhUdysxNURYUWM0RDJycEx1Q3gvSzNGNXN2YUN5djY3UlhiTm5YOVJiRGo3a1cKNm5rRVg5SmVzdFRZbU9oQnB4MjRqQXVHSnV5bmFGUlhnNnJscG80dlBBVTYvZGw2d1hnQlBRNXFvZnJVVWlZcwo5T1NhS051VWdvRzZOMVVWSUYvS25Ya2M5UUpYbG1xSE1JMGovWE1FM01xZ2xuejY5ZUlCVjZqUVRFMldENW1tCllQQmpSdSs2eUxJVTZUUXNzZUZPSmJNY0thd0l6eEoxTlNkOVU2L25Ba3VZTWtzTGhlSllmd2xhQkZaL0ZweksKS3hoM0xQTVZRTFhEVjIzODlIclcxS3lTTitFdUMyVWJ3cllYTWttVHY4NUhUbkhoN3RvWG0xWkRSTTRlODE4egp5dnRnRmNPRFBtUS84ekwyMWpPdzRrei9RNDBNUGd4OW1vMGw0K3BmQzZTRjF6MUdiMER0OWtWT2VkdFkwdlVZCmJJZWVoV1BWSldnOThHRjdXOTFrcTFwQjN3b2tqdjZWa1ZHM0syMnE1QUhudnVsTjRITE92Y0djV1FLOGFwZVMKK3Z2UDVBdjJBM3JHSjBZSUdmL05ycDRaTFpHMHBYYlI4UVdNS2RkdUVVM1pYdFlxV0dTWEx0MlpyKzdNakFFdwpyQ2VjaVcraHNwMzArcUpJcGp4VUpBNXVCdFFyMHBDMGtrTkRITEdJSEpmcG5WRUU2MkJMSGFjczVVRkFsOFg0CmdhanV2MUlyQ0lMVFQ3cVZSczB6OEJaUE1uemd5SVJtYmF6K05PbjlhaGFTOVhFUWE3b0JHVDRwV2NUVHNhelMKUXdHMVdRdjNMMG54bjBMcmpoZGpPSnE1a2QxcDdpQlkwd0trRHZlNU1aUUxHK09FZ0IyYkhNZlloYUZzL21pMQppMnNndHRTYTByMWNTaGl3M0NPWVpSRmp5V3M9Cj1wWHlRCi0tLS0tRU5EIFBHUCBNRVNTQUdFLS0tLS0K2020-08-09T16:57:05.766Z: Access granted to LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tCgpoUUlNQXhUMDlUUngzbFo2QVEvL2RzNG5QVW5KMk1YT0pXQlVhNVUvS0drMjJnRjQ3SkxBSTM1VzhkN1NWWndvCkhhN2phZHZVM1BaNEFuaktVQXhUdysxNURYUWM0RDJycEx1Q3gvSzNGNXN2YUN5djY3UlhiTm5YOVJiRGo3a1cKNm5rRVg5SmVzdFRZbU9oQnB4MjRqQXVHSnV5bmFGUlhnNnJscG80dlBBVTYvZGw2d1hnQlBRNXFvZnJVVWlZcwo5T1NhS051VWdvRzZOMVVWSUYvS25Ya2M5UUpYbG1xSE1JMGovWE1FM01xZ2xuejY5ZUlCVjZqUVRFMldENW1tCllQQmpSdSs2eUxJVTZUUXNzZUZPSmJNY0thd0l6eEoxTlNkOVU2L25Ba3VZTWtzTGhlSllmd2xhQkZaL0ZweksKS3hoM0xQTVZRTFhEVjIzODlIclcxS3lTTitFdUMyVWJ3cllYTWttVHY4NUhUbkhoN3RvWG0xWkRSTTRlODE4egp5dnRnRmNPRFBtUS84ekwyMWpPdzRrei9RNDBNUGd4OW1vMGw0K3BmQzZTRjF6MUdiMER0OWtWT2VkdFkwdlVZCmJJZWVoV1BWSldnOThHRjdXOTFrcTFwQjN3b2tqdjZWa1ZHM0syMnE1QUhudnVsTjRITE92Y0djV1FLOGFwZVMKK3Z2UDVBdjJBM3JHSjBZSUdmL05ycDRaTFpHMHBYYlI4UVdNS2RkdUVVM1pYdFlxV0dTWEx0MlpyKzdNakFFdwpyQ2VjaVcraHNwMzArcUpJcGp4VUpBNXVCdFFyMHBDMGtrTkRITEdJSEpmcG5WRUU2MkJMSGFjczVVRkFsOFg0CmdhanV2MUlyQ0lMVFQ3cVZSczB6OEJaUE1uemd5SVJtYmF6K05PbjlhaGFTOVhFUWE3b0JHVDRwV2NUVHNhelMKUXdHMVdRdjNMMG54bjBMcmpoZGpPSnE1a2QxcDdpQlkwd0trRHZlNU1aUUxHK09FZ0IyYkhNZlloYUZzL21pMQppMnNndHRTYTByMWNTaGl3M0NPWVpSRmp5V3M9Cj1wWHlRCi0tLS0tRU5EIFBHUCBNRVNTQUdFLS0tLS0K2020-08-09T16:57:05.930Z: Access granted to LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tCgpoUUlNQXhUMDlUUngzbFo2QVEvL2RzNG5QVW5KMk1YT0pXQlVhNVUvS0drMjJnRjQ3SkxBSTM1VzhkN1NWWndvCkhhN2phZHZVM1BaNEFuaktVQXhUdysxNURYUWM0RDJycEx1Q3gvSzNGNXN2YUN5djY3UlhiTm5YOVJiRGo3a1cKNm5rRVg5SmVzdFRZbU9oQnB4MjRqQXVHSnV5bmFGUlhnNnJscG80dlBBVTYvZGw2d1hnQlBRNXFvZnJVVWlZcwo5T1NhS051VWdvRzZOMVVWSUYvS25Ya2M5UUpYbG1xSE1JMGovWE1FM01xZ2xuejY5ZUlCVjZqUVRFMldENW1tCllQQmpSdSs2eUxJVTZUUXNzZUZPSmJNY0thd0l6eEoxTlNkOVU2L25Ba3VZTWtzTGhlSllmd2xhQkZaL0ZweksKS3hoM0xQTVZRTFhEVjIzODlIclcxS3lTTitFdUMyVWJ3cllYTWttVHY4NUhUbkhoN3RvWG0xWkRSTTRlODE4egp5dnRnRmNPRFBtUS84ekwyMWpPdzRrei9RNDBNUGd4OW1vMGw0K3BmQzZTRjF6MUdiMER0OWtWT2VkdFkwdlVZCmJJZWVoV1BWSldnOThHRjdXOTFrcTFwQjN3b2tqdjZWa1ZHM0syMnE1QUhudnVsTjRITE92Y0djV1FLOGFwZVMKK3Z2UDVBdjJBM3JHSjBZSUdmL05ycDRaTFpHMHBYYlI4UVdNS2RkdUVVM1pYdFlxV0dTWEx0MlpyKzdNakFFdwpyQ2VjaVcraHNwMzArcUpJcGp4VUpBNXVCdFFyMHBDMGtrTkRITEdJSEpmcG5WRUU2MkJMSGFjczVVRkFsOFg0CmdhanV2MUlyQ0lMVFQ3cVZSczB6OEJaUE1uemd5SVJtYmF6K05PbjlhaGFTOVhFUWE3b0JHVDRwV2NUVHNhelMKUXdHMVdRdjNMMG54bjBMcmpoZGpPSnE1a2QxcDdpQlkwd0trRHZlNU1aUUxHK09FZ0IyYkhNZlloYUZzL21pMQppMnNndHRTYTByMWNTaGl3M0NPWVpSRmp5V3M9Cj1wWHlRCi0tLS0tRU5EIFBHUCBNRVNTQUdFLS0tLS0K2020-08-09T16:57:06.118Z: Access granted to LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tCgpoUUlNQXhUMDlUUngzbFo2QVEvL2RzNG5QVW5KMk1YT0pXQlVhNVUvS0drMjJnRjQ3SkxBSTM1VzhkN1NWWndvCkhhN2phZHZVM1BaNEFuaktVQXhUdysxNURYUWM0RDJycEx1Q3gvSzNGNXN2YUN5djY3UlhiTm5YOVJiRGo3a1cKNm5rRVg5SmVzdFRZbU9oQnB4MjRqQXVHSnV5bmFGUlhnNnJscG80dlBBVTYvZGw2d1hnQlBRNXFvZnJVVWlZcwo5T1NhS051VWdvRzZOMVVWSUYvS25Ya2M5UUpYbG1xSE1JMGovWE1FM01xZ2xuejY5ZUlCVjZqUVRFMldENW1tCllQQmpSdSs2eUxJVTZUUXNzZUZPSmJNY0thd0l6eEoxTlNkOVU2L25Ba3VZTWtzTGhlSllmd2xhQkZaL0ZweksKS3hoM0xQTVZRTFhEVjIzODlIclcxS3lTTitFdUMyVWJ3cllYTWttVHY4NUhUbkhoN3RvWG0xWkRSTTRlODE4egp5dnRnRmNPRFBtUS84ekwyMWpPdzRrei9RNDBNUGd4OW1vMGw0K3BmQzZTRjF6MUdiMER0OWtWT2VkdFkwdlVZCmJJZWVoV1BWSldnOThHRjdXOTFrcTFwQjN3b2tqdjZWa1ZHM0syMnE1QUhudnVsTjRITE92Y0djV1FLOGFwZVMKK3Z2UDVBdjJBM3JHSjBZSUdmL05ycDRaTFpHMHBYYlI4UVdNS2RkdUVVM1pYdFlxV0dTWEx0MlpyKzdNakFFdwpyQ2VjaVcraHNwMzArcUpJcGp4VUpBNXVCdFFyMHBDMGtrTkRITEdJSEpmcG5WRUU2MkJMSGFjczVVRkFsOFg0CmdhanV2MUlyQ0lMVFQ3cVZSczB6OEJaUE1uemd5SVJtYmF6K05PbjlhaGFTOVhFUWE3b0JHVDRwV2NUVHNhelMKUXdHMVdRdjNMMG54bjBMcmpoZGpPSnE1a2QxcDdpQlkwd0trRHZlNU1aUUxHK09FZ0IyYkhNZlloYUZzL21pMQppMnNndHRTYTByMWNTaGl3M0NPWVpSRmp5V3M9Cj1wWHlRCi0tLS0tRU5EIFBHUCBNRVNTQUdFLS0tLS0K2020-08-09T16:57:06.293Z: Access granted to LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tCgpoUUlNQXhUMDlUUngzbFo2QVEvL2RzNG5QVW5KMk1YT0pXQlVhNVUvS0drMjJnRjQ3SkxBSTM1VzhkN1NWWndvCkhhN2phZHZVM1BaNEFuaktVQXhUdysxNURYUWM0RDJycEx1Q3gvSzNGNXN2YUN5djY3UlhiTm5YOVJiRGo3a1cKNm5rRVg5SmVzdFRZbU9oQnB4MjRqQXVHSnV5bmFGUlhnNnJscG80dlBBVTYvZGw2d1hnQlBRNXFvZnJVVWlZcwo5T1NhS051VWdvRzZOMVVWSUYvS25Ya2M5UUpYbG1xSE1JMGovWE1FM01xZ2xuejY5ZUlCVjZqUVRFMldENW1tCllQQmpSdSs2eUxJVTZUUXNzZUZPSmJNY0thd0l6eEoxTlNkOVU2L25Ba3VZTWtzTGhlSllmd2xhQkZaL0ZweksKS3hoM0xQTVZRTFhEVjIzODlIclcxS3lTTitFdUMyVWJ3cllYTWttVHY4NUhUbkhoN3RvWG0xWkRSTTRlODE4egp5dnRnRmNPRFBtUS84ekwyMWpPdzRrei9RNDBNUGd4OW1vMGw0K3BmQzZTRjF6MUdiMER0OWtWT2VkdFkwdlVZCmJJZWVoV1BWSldnOThHRjdXOTFrcTFwQjN3b2tqdjZWa1ZHM0syMnE1QUhudnVsTjRITE92Y0djV1FLOGFwZVMKK3Z2UDVBdjJBM3JHSjBZSUdmL05ycDRaTFpHMHBYYlI4UVdNS2RkdUVVM1pYdFlxV0dTWEx0MlpyKzdNakFFdwpyQ2VjaVcraHNwMzArcUpJcGp4VUpBNXVCdFFyMHBDMGtrTkRITEdJSEpmcG5WRUU2MkJMSGFjczVVRkFsOFg0CmdhanV2MUlyQ0lMVFQ3cVZSczB6OEJaUE1uemd5SVJtYmF6K05PbjlhaGFTOVhFUWE3b0JHVDRwV2NUVHNhelMKUXdHMVdRdjNMMG54bjBMcmpoZGpPSnE1a2QxcDdpQlkwd0trRHZlNU1aUUxHK09FZ0IyYkhNZlloYUZzL21pMQppMnNndHRTYTByMWNTaGl3M0NPWVpSRmp5V3M9Cj1wWHlRCi0tLS0tRU5EIFBHUCBNRVNTQUdFLS0tLS0K2020-08-09T16:57:06.472Z: Access granted to LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tCgpoUUlNQXhUMDlUUngzbFo2QVEvL2RzNG5QVW5KMk1YT0pXQlVhNVUvS0drMjJnRjQ3SkxBSTM1VzhkN1NWWndvCkhhN2phZHZVM1BaNEFuaktVQXhUdysxNURYUWM0RDJycEx1Q3gvSzNGNXN2YUN5djY3UlhiTm5YOVJiRGo3a1cKNm5rRVg5SmVzdFRZbU9oQnB4MjRqQXVHSnV5bmFGUlhnNnJscG80dlBBVTYvZGw2d1hnQlBRNXFvZnJVVWlZcwo5T1NhS051VWdvRzZOMVVWSUYvS25Ya2M5UUpYbG1xSE1JMGovWE1FM01xZ2xuejY5ZUlCVjZqUVRFMldENW1tCllQQmpSdSs2eUxJVTZUUXNzZUZPSmJNY0thd0l6eEoxTlNkOVU2L25Ba3VZTWtzTGhlSllmd2xhQkZaL0ZweksKS3hoM0xQTVZRTFhEVjIzODlIclcxS3lTTitFdUMyVWJ3cllYTWttVHY4NUhUbkhoN3RvWG0xWkRSTTRlODE4egp5dnRnRmNPRFBtUS84ekwyMWpPdzRrei9RNDBNUGd4OW1vMGw0K3BmQzZTRjF6MUdiMER0OWtWT2VkdFkwdlVZCmJJZWVoV1BWSldnOThHRjdXOTFrcTFwQjN3b2tqdjZWa1ZHM0syMnE1QUhudnVsTjRITE92Y0djV1FLOGFwZVMKK3Z2UDVBdjJBM3JHSjBZSUdmL05ycDRaTFpHMHBYYlI4UVdNS2RkdUVVM1pYdFlxV0dTWEx0MlpyKzdNakFFdwpyQ2VjaVcraHNwMzArcUpJcGp4VUpBNXVCdFFyMHBDMGtrTkRITEdJSEpmcG5WRUU2MkJMSGFjczVVRkFsOFg0CmdhanV2MUlyQ0lMVFQ3cVZSczB6OEJaUE1uemd5SVJtYmF6K05PbjlhaGFTOVhFUWE3b0JHVDRwV2NUVHNhelMKUXdHMVdRdjNMMG54bjBMcmpoZGpPSnE1a2QxcDdpQlkwd0trRHZlNU1aUUxHK09FZ0IyYkhNZlloYUZzL21pMQppMnNndHRTYTByMWNTaGl3M0NPWVpSRmp5V3M9Cj1wWHlRCi0tLS0tRU5EIFBHUCBNRVNTQUdFLS0tLS0K2020-08-09T16:57:06.831Z: Access granted to LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tCgpoUUlNQXhUMDlUUngzbFo2QVEvL2RzNG5QVW5KMk1YT0pXQlVhNVUvS0drMjJnRjQ3SkxBSTM1VzhkN1NWWndvCkhhN2phZHZVM1BaNEFuaktVQXhUdysxNURYUWM0RDJycEx1Q3gvSzNGNXN2YUN5djY3UlhiTm5YOVJiRGo3a1cKNm5rRVg5SmVzdFRZbU9oQnB4MjRqQXVHSnV5bmFGUlhnNnJscG80dlBBVTYvZGw2d1hnQlBRNXFvZnJVVWlZcwo5T1NhS051VWdvRzZOMVVWSUYvS25Ya2M5UUpYbG1xSE1JMGovWE1FM01xZ2xuejY5ZUlCVjZqUVRFMldENW1tCllQQmpSdSs2eUxJVTZUUXNzZUZPSmJNY0thd0l6eEoxTlNkOVU2L25Ba3VZTWtzTGhlSllmd2xhQkZaL0ZweksKS3hoM0xQTVZRTFhEVjIzODlIclcxS3lTTitFdUMyVWJ3cllYTWttVHY4NUhUbkhoN3RvWG0xWkRSTTRlODE4egp5dnRnRmNPRFBtUS84ekwyMWpPdzRrei9RNDBNUGd4OW1vMGw0K3BmQzZTRjF6MUdiMER0OWtWT2VkdFkwdlVZCmJJZWVoV1BWSldnOThHRjdXOTFrcTFwQjN3b2tqdjZWa1ZHM0syMnE1QUhudnVsTjRITE92Y0djV1FLOGFwZVMKK3Z2UDVBdjJBM3JHSjBZSUdmL05ycDRaTFpHMHBYYlI4UVdNS2RkdUVVM1pYdFlxV0dTWEx0MlpyKzdNakFFdwpyQ2VjaVcraHNwMzArcUpJcGp4VUpBNXVCdFFyMHBDMGtrTkRITEdJSEpmcG5WRUU2MkJMSGFjczVVRkFsOFg0CmdhanV2MUlyQ0lMVFQ3cVZSczB6OEJaUE1uemd5SVJtYmF6K05PbjlhaGFTOVhFUWE3b0JHVDRwV2NUVHNhelMKUXdHMVdRdjNMMG54bjBMcmpoZGpPSnE1a2QxcDdpQlkwd0trRHZlNU1aUUxHK09FZ0IyYkhNZlloYUZzL21pMQppMnNndHRTYTByMWNTaGl3M0NPWVpSRmp5V3M9Cj1wWHlRCi0tLS0tRU5EIFBHUCBNRVNTQUdFLS0tLS0K
server/src/Auth/Handler.pursnew file+46
@@ -0,0 +1,46 @@
1+module Auth.Handler where
2+
3+import Prelude
4+import Auth.Types (Token(..))
5+import Data.Array (elem)
6+import Data.JSDate (now, toISOString)
7+import Data.Maybe (Maybe(..))
8+import Data.String (Pattern(..), stripPrefix)
9+import Data.String.Utils (lines)
10+import Effect.Aff (Aff)
11+import Effect.Aff.Class (liftAff)
12+import Effect.Class (liftEffect)
13+import Node.Encoding (Encoding(..))
14+import Node.Express.Handler (HandlerM, next)
15+import Node.Express.Request (getRequestHeader)
16+import Node.Express.Response (end, setStatus)
17+import Node.Express.Response as Response
18+import Node.FS.Aff (appendTextFile, readTextFile)
19+import Simple.JSON (write)
20+
21+readAllowedTokens ∷ Aff (Array Token)
22+readAllowedTokens = do
23+ text <- readTextFile UTF8 ".authorized-keys"
24+ pure $ Token <$> lines text
25+
26+authHandler ∷ Array Token -> HandlerM Unit
27+authHandler authorizedTokens = do
28+ authHeader <- getRequestHeader "Authorization"
29+ let maybeToken = authHeader >>= stripPrefix (Pattern "Bearer ") <#> Token
30+ ts <- now >>= toISOString # liftEffect
31+ let log s = liftAff $ appendTextFile UTF8 "access.log" $ ts <> ": " <> s
32+ case maybeToken of
33+ Nothing -> do
34+ setStatus 401
35+ log $ "Denied request without token"
36+ Response.send $ write { error: "Jetzt hör'n Sie mir mal zu! Erstens brauch ich ihren Namen!!" }
37+ end
38+ Just token@(Token raw)
39+ | elem token authorizedTokens -> do
40+ log $ "Access granted to " <> raw
41+ next
42+ Just (Token invalid) -> do
43+ setStatus 403
44+ log $ "Access denied to " <> invalid
45+ Response.send $ write { error: "Ich seh' ja ein das man hier der gläserne Mensch ist." }
46+ end
server/src/Auth/Types.pursnew file+7
@@ -0,0 +1,7 @@
1+module Auth.Types where
2+
3+import Prelude
4+
5+newtype Token = Token String
6+
7+derive instance eqToken ∷ Eq Token
server/src/JobQueue.purs+25−30
@@ -1,7 +1,6 @@
11module JobQueue where
22
33import Prelude
4−
54import Data.Array (delete, filter, find, length, snoc, zip)
65import Data.Foldable (for_)
76import Data.Generic.Rep (class Generic)
@@ -19,17 +18,17 @@ import Effect.Class (class MonadEffect, liftEffect)
1918import Effect.Ref (Ref)
2019import Effect.Ref as Ref
2120
22−newtype JobId
23− = JobId UUID
21+newtype JobId = JobId UUID
22+
23+derive instance ntJobId ∷ Newtype JobId _
24+derive instance genericJobId ∷ Generic JobId _
2425
25−derive instance ntJobId :: Newtype JobId _
26−derive instance genericJobId :: Generic JobId _
27−instance showJobId :: Show JobId where
26+instance showJobId ∷ Show JobId where
2827 show = genericShow
2928
30−derive newtype instance jobIdEq :: Eq JobId
31−newtype NewJob a
32− = NewJob (JobId -> a -> Aff Unit)
29+derive newtype instance jobIdEq ∷ Eq JobId
30+
31+newtype NewJob a = NewJob (JobId -> a -> Aff Unit)
3332
3433data PendingJob a
3534 = PendingJob
@@ -47,10 +46,10 @@ getAddedAt = case _ of
4746 Pending (PendingJob { addedAt }) -> addedAt
4847 Running (RunningJob { addedAt }) -> addedAt
4948
50−instance eqJob :: Eq (Job a) where
49+instance eqJob ∷ Eq (Job a) where
5150 eq j1 j2 = getId j1 == getId j2
5251
53−instance ordJob :: Ord (Job a) where
52+instance ordJob ∷ Ord (Job a) where
5453 compare j1 j2 = compare (getAddedAt j1) (getAddedAt j2)
5554
5655getId ∷ ∀ a. Job a -> JobId
@@ -58,8 +57,7 @@ getId = case _ of
5857 Pending (PendingJob { id }) -> id
5958 Running (RunningJob { id }) -> id
6059
61−newtype RunningJob a
62− = RunningJob
60+newtype RunningJob a = RunningJob
6361 { id ∷ JobId
6462 , addedAt ∷ JSDate
6563 , startedAt ∷ JSDate
@@ -67,15 +65,15 @@ newtype RunningJob a
6765 , resource ∷ a
6866 }
6967
70−type QueueParams
71− = { maxSize ∷ Int
72− , timeout ∷ Milliseconds
73− }
68+type QueueParams =
69+ { maxSize ∷ Int
70+ , timeout ∷ Milliseconds
71+ }
72+
73+newtype ResourcePool a = ResourcePool (Array a)
7474
75−newtype ResourcePool a
76− = ResourcePool (Array a)
75+derive instance ntResourcePool ∷ Newtype (ResourcePool a) _
7776
78−derive instance ntResourcePool :: Newtype (ResourcePool a) _
7977data Queue a
8078 = Queue QueueParams (Ref (ResourcePool a)) (Ref (Array (Job a)))
8179
@@ -90,9 +88,10 @@ data EnqueueResult
9088 = Enqueued JobId
9189 | QueueFull
9290
93−derive instance eqEnqueueResult :: Eq EnqueueResult
94−derive instance genericEnqueueResult :: Generic EnqueueResult _
95−instance showEnqueueResult :: Show EnqueueResult where
91+derive instance eqEnqueueResult ∷ Eq EnqueueResult
92+derive instance genericEnqueueResult ∷ Generic EnqueueResult _
93+
94+instance showEnqueueResult ∷ Show EnqueueResult where
9695 show = genericShow
9796
9897startJob ∷ ∀ a. Eq a => Queue a -> a -> PendingJob a -> Effect Unit
@@ -100,14 +99,12 @@ startJob q@(Queue { timeout } _ jobsRef) resource (PendingJob { id, job, addedAt
10099 startedAt <- JSDate.now
101100 iso <- JSDate.toISOString startedAt
102101 -- log $ "Starting Job: " <> toString (unwrap id) <> " at: " <> iso
103− let
104− remove = removeFromQueue id q
102+ let remove = removeFromQueue id q
105103 fiber <-
106104 launchAff <<< sequential
107105 $ parallel (job resource *> remove)
108106 *> parallel (delay timeout *> remove)
109− let
110− running = RunningJob { id, fiber, addedAt, startedAt, resource }
107+ let running = RunningJob { id, fiber, addedAt, startedAt, resource }
111108 jobsRef
112109 # Ref.modify_ (map \j -> if getId j == id then Running running else j)
113110
@@ -151,7 +148,6 @@ removeStaleJobs q@(Queue { timeout } resourceRef jobsRef) = do
151148 $ \(RunningJob { startedAt, id }) -> do
152149 let
153150 runningSince = (nowMillis - JSDate.getTime startedAt) # Milliseconds
154−
155151 timedOut = runningSince > timeout
156152 when timedOut $ launchAff_ (removeFromQueue id q)
157153
@@ -179,8 +175,7 @@ enqueue (NewJob newJob) q@(Queue { maxSize, timeout } _ jobsRef) = do
179175 pure QueueFull
180176 else do
181177 addedAt <- JSDate.now
182− let
183− new = Pending (PendingJob { id, addedAt, job: newJob id })
178+ let new = Pending (PendingJob { id, addedAt, job: newJob id })
184179 jobsRef # Ref.modify_ (_ `snoc` new)
185180 startJobs q
186181 pure (Enqueued id)
server/src/JobQueueSpec.purs+10−14
@@ -1,7 +1,6 @@
11module JobQueueSpec where
22
33import Prelude
4−
54import Data.Time.Duration (class Duration, Seconds(..), fromDuration)
65import Effect.Aff (Milliseconds(..), delay)
76import Effect.Class (liftEffect)
@@ -36,14 +35,14 @@ writeRefJob ref a d =
3635 delay millis
3736 Ref.write a ref # liftEffect
3837
39−pool :: ResourcePool Unit
40−pool = ResourcePool [unit, unit, unit]
38+pool ∷ ResourcePool Unit
39+pool = ResourcePool [ unit, unit, unit ]
4140
4241spec ∷ Spec Unit
4342spec = do
4443 describe "The Queue" do
4544 it "can't enqueue if maxSize is 0" do
46− q <- mkQueue (params { maxSize = 0 }) pool
45+ q <- mkQueue (params { maxSize = 0 }) pool
4746 r <- Q.enqueue job1 q # liftEffect
4847 r `shouldEqual` QueueFull
4948 it "can enqueue if maxSize is 1" do
@@ -67,10 +66,9 @@ spec = do
6766 delay (9.0 # Milliseconds)
6867 res `shouldEqual` false
6968 it "runs multiple jobs" do
70− q <- mkQueue (params { maxSize = 3, timeout = (2.0 # Seconds # fromDuration) }) (ResourcePool [unit])
69+ q <- mkQueue (params { maxSize = 3, timeout = (2.0 # Seconds # fromDuration) }) (ResourcePool [ unit ])
7170 ref <- Ref.new 0 # liftEffect
72− let
73− enq n delay = Q.enqueue (writeRefJob ref n delay) q # liftEffect
71+ let enq n delay = Q.enqueue (writeRefJob ref n delay) q # liftEffect
7472 r₁ <- enq 1 (0.1 # Seconds)
7573 r₁ `shouldNotEqual` QueueFull
7674 r₂ <- enq 2 (0.2 # Seconds)
@@ -85,10 +83,9 @@ spec = do
8583 delay (0.3 # Seconds # fromDuration)
8684 Ref.read ref # liftEffect >>= shouldEqual 3 -- ~350ms
8785 it "respects pool size" do
88− q <- mkQueue (params { maxSize = 3, timeout = (2.0 # Seconds # fromDuration) }) (ResourcePool [unit])
86+ q <- mkQueue (params { maxSize = 3, timeout = (2.0 # Seconds # fromDuration) }) (ResourcePool [ unit ])
8987 ref <- Ref.new 0 # liftEffect
90− let
91− enq n delay = Q.enqueue (writeRefJob ref n delay) q # liftEffect
88+ let enq n delay = Q.enqueue (writeRefJob ref n delay) q # liftEffect
9289 r₁ <- enq 1 (0.1 # Seconds)
9390 r₁ `shouldNotEqual` QueueFull
9491 r₂ <- enq 2 (0.1 # Seconds)
@@ -103,10 +100,9 @@ spec = do
103100 delay (0.3 # Seconds # fromDuration)
104101 Ref.read ref # liftEffect >>= shouldEqual 3 -- ~350ms
105102 it "respects pool size (2)" do
106− q <- mkQueue (params { maxSize = 3, timeout = (2.0 # Seconds # fromDuration) }) (ResourcePool [unit, unit, unit])
103+ q <- mkQueue (params { maxSize = 3, timeout = (2.0 # Seconds # fromDuration) }) (ResourcePool [ unit, unit, unit ])
107104 ref <- Ref.new 0 # liftEffect
108− let
109− enq n delay = Q.enqueue (writeRefJob ref n delay) q # liftEffect
105+ let enq n delay = Q.enqueue (writeRefJob ref n delay) q # liftEffect
110106 r₁ <- enq 1 (0.1 # Seconds)
111107 r₂ <- enq 2 (0.1 # Seconds)
112108 r₃ <- enq 3 (0.1 # Seconds)
@@ -117,7 +113,7 @@ spec = do
117113 delay (0.15 # Seconds # fromDuration)
118114 Ref.read ref # liftEffect >>= shouldEqual 3 -- ~150ms
119115 it "can kill processes" do
120− q <- mkQueue (params { maxSize = 1, timeout = (1.0 # Seconds # fromDuration) }) (ResourcePool [unit])
116+ q <- mkQueue (params { maxSize = 1, timeout = (1.0 # Seconds # fromDuration) }) (ResourcePool [ unit ])
121117 resultRef <- Ref.new false # liftEffect
122118 let
123119 job =
server/src/Main.purs+43−57
@@ -1,9 +1,10 @@
11module Main where
22
33import Prelude
4−
4+import Auth.Handler (authHandler, readAllowedTokens)
5+import Auth.Types (Token(..))
56import Control.Parallel (parOneOf, parTraverse)
6−import Data.Array ((..))
7+import Data.Array (elem, (..))
78import Data.Bifunctor (lmap)
89import Data.Either (Either(..))
910import Data.Int (fromString)
@@ -25,10 +26,10 @@ import Node.ChildProcess (ExecResult)
2526import Node.Encoding (Encoding(..))
2627import Node.Express.App (App, listenHttp, listenHttps, use, useExternal)
2728import Node.Express.App as E
28−import Node.Express.Handler (HandlerM(..), Handler)
29+import Node.Express.Handler (Handler, HandlerM(..), next)
2930import Node.Express.Middleware.Static (static)
30−import Node.Express.Request (getBody')
31−import Node.Express.Response (setStatus)
31+import Node.Express.Request (getBody', getRequestHeader)
32+import Node.Express.Response (end, setStatus)
3233import Node.Express.Response as Response
3334import Node.Express.Types (Request, Response)
3435import Node.FS.Aff (readTextFile)
@@ -48,40 +49,41 @@ toBody result =
4849 $ ado
4950 stdout <- Buffer.toString UTF8 result.stdout
5051 stderr <- Buffer.toString UTF8 result.stderr
51− let
52− (code ∷ Maybe Int) = asErrorWithCode result >>= _.code
52+ let (code ∷ Maybe Int) = asErrorWithCode result >>= _.code
5353 in { code, stdout, stderr } ∷ RunResult
5454
55−type ErrorWithCode
56− = { code ∷ Maybe Int }
55+type ErrorWithCode =
56+ { code ∷ Maybe Int }
5757
5858asErrorWithCode ∷ ∀ a. a -> Maybe ErrorWithCode
5959asErrorWithCode = read_ <<< unsafeToForeign
6060
6161runCode ∷ ∀ d. Duration d => d -> Folder -> Aff (Maybe ExecResult)
62−runCode timeout folder = parOneOf
63− [ Just <$> execCommand folder "exec node run.js"
64− , Nothing <$ delay (timeout # fromDuration)
65− ]
62+runCode timeout folder =
63+ parOneOf
64+ [ Just <$> execCommand folder "exec node run.js"
65+ , Nothing <$ delay (timeout # fromDuration)
66+ ]
6667
6768compileAndRunJob ∷ CompileRequest -> (Handler -> Aff Unit) -> NewJob PscIdeConnection
6869compileAndRunJob json handle =
6970 NewJob \jobId conn -> do
7071 stringOrErr <- attempt $ compileCode json.code conn
71− case ((readJSON <$> stringOrErr) ∷ _ _ (_ _ CompileResult)) of
72+ case ((readJSON <$> stringOrErr) ∷ _ _ (_ _ CompileResult)) of
7273 Left e -> do
7374 handle $ setStatus 500
7475 log $ "Aff failed with " <> message e
7576 handle $ Response.send $ write {}
76− Right (Right res) | res.resultType == "error" -> do
77− handle $ setStatus 422
78− handle $ Response.send $ write res
77+ Right (Right res)
78+ | res.resultType == "error" -> do
79+ handle $ setStatus 422
80+ handle $ Response.send $ write res
7981 Right (Right res) -> do
80− runResult <- runCode timeout (getFolder conn)
82+ runResult <- runCode timeout (getFolder conn)
8183 case runResult of
8284 Nothing -> do
8385 handle $ setStatus 408
84− handle $ Response.send $ write { error: "Timed out after running for " <> show timeout}
86+ handle $ Response.send $ write { error: "Timed out after running for " <> show timeout }
8587 Just rr -> do
8688 resultBody <- toBody rr
8789 handle $ Response.send $ write (resultBody ∷ Body.RunResult)
@@ -89,8 +91,8 @@ compileAndRunJob json handle =
8991 handle $ setStatus 500
9092 log $ "Could not decode: " <> show (lmap (const "no way") stringOrErr) <> "\nErrors: " <> show errs
9193 handle $ Response.send $ write {}
92− where
93− timeout = 1.0 # Seconds
94+ where
95+ timeout = 1.0 # Seconds
9496
9597compileAndRunHandler ∷ Queue PscIdeConnection -> Handler
9698compileAndRunHandler queue = do
@@ -99,7 +101,6 @@ compileAndRunHandler queue = do
99101 HandlerM \req res next -> do
100102 let
101103 handle = unHandler req res next
102−
103104 runJob = compileAndRunJob json handle
104105 queueRes <- Q.enqueue runJob queue # liftEffect
105106 case queueRes of
@@ -121,19 +122,20 @@ destFolder = "../playgrounds/"
121122main ∷ Effect Unit
122123main = do
123124 launchAff_ do
125+ tokens <- readAllowedTokens
124126 cpus <- numCpus # liftEffect
125127 let
126128 poolSize = max 2 (cpus / 2) -- use at least 2
127−
128129 mkFolder = Folder <<< (destFolder <> _) <<< show
129−
130− connections <- (1 .. poolSize) # parTraverse \n -> do
131− let folder = mkFolder n
132− port = 14100 + n
133− log $ "Copying to folder " <> (un Folder folder)
134− copy srcFolder (un Folder folder)
135− mkConnection folder port
136−
130+ connections <-
131+ (1 .. poolSize)
132+ # parTraverse \n -> do
133+ let
134+ folder = mkFolder n
135+ port = 14100 + n
136+ log $ "Copying to folder " <> (un Folder folder)
137+ copy srcFolder (un Folder folder)
138+ mkConnection folder port
137139 let pool = ResourcePool connections
138140 q <-
139141 Q.mkQueue
@@ -141,38 +143,22 @@ main = do
141143 , timeout: 10.0 # Seconds # fromDuration
142144 }
143145 pool
144− serverSetup (makeApp q)
146+ serverSetup (makeApp tokens q)
145147
146−makeApp ∷ Queue PscIdeConnection -> App
147−makeApp q = do
148+makeApp ∷ Array Token -> Queue PscIdeConnection -> App
149+makeApp tokens q = do
148150 use $ static "assets"
149151 useExternal jsonBodyParser
152+ use (authHandler tokens)
150153 E.post "/compileAndRun" (compileAndRunHandler q)
151154
152−data CertificatesToUse
153− = UseLocalCertificates
154− | DoNotUseLocalCertificates
155−
156−parseCertificatesToUse ∷ String -> CertificatesToUse
157−parseCertificatesToUse = case _ of
158− "LOCAL" -> UseLocalCertificates
159− _ -> DoNotUseLocalCertificates
160−
161155serverSetup ∷ App -> Aff Server
162156serverSetup app = do
163157 maybePortString <- lookupEnv "PORT" # liftEffect
164− let
165− port = maybePortString >>= fromString # fromMaybe 14188
166− useLocalCertificates <- fromMaybe DoNotUseLocalCertificates <<< map parseCertificatesToUse <$> lookupEnv "CERTS" # liftEffect
167− listen <- case useLocalCertificates of
168− UseLocalCertificates -> do
169− httpsOptions <- makeHttpsOptions
170− pure $ listenHttps app (port ∷ Int) httpsOptions
171− DoNotUseLocalCertificates -> pure $ listenHttp app port
172− liftEffect $ listen \_ -> info $ "psfp server started on port " <> show port
158+ let port = maybePortString >>= fromString # fromMaybe 14188
159+ liftEffect $ (listenHttp app port) \_ -> info $ "psfp server started on port " <> show port
173160 where
174− makeHttpsOptions = do
175− key <- readTextFile UTF8 "server.key"
176− cert <- readTextFile UTF8 "server.cert"
177− pure { key, cert }
178−
161+ makeHttpsOptions = do
162+ key <- readTextFile UTF8 "server.key"
163+ cert <- readTextFile UTF8 "server.cert"
164+ pure { key, cert }
server/src/Middleware/JsonBodyParser.purs+1−2
@@ -1,9 +1,8 @@
11module Middleware.JsonBodyParser where
22
33import Prelude
4−
54import Data.Function.Uncurried (Fn3)
65import Effect (Effect)
76import Node.Express.Types (Request, Response)
87
9−foreign import jsonBodyParser :: Fn3 Request Response (Effect Unit) (Effect Unit)
8+foreign import jsonBodyParser ∷ Fn3 Request Response (Effect Unit) (Effect Unit)
server/src/Node/OS.purs+1−1
@@ -2,4 +2,4 @@ module Node.OS where
22
33import Effect (Effect)
44
5−foreign import numCpus :: Effect Int
5+foreign import numCpus ∷ Effect Int
server/src/Playground/Playground.purs+7−8
@@ -1,18 +1,17 @@
11module Playground.Playground where
22
33import Prelude
4−
54import Control.Promise (Promise, toAff)
65import Data.Function.Uncurried (Fn2, runFn2)
76import Data.Newtype (class Newtype)
87import Effect.Aff (Aff)
98
10−newtype Folder
11− = Folder String
12−derive instance ntFolder :: Newtype Folder _
13−derive newtype instance eqFolder :: Eq Folder
9+newtype Folder = Folder String
10+
11+derive instance ntFolder ∷ Newtype Folder _
12+derive newtype instance eqFolder ∷ Eq Folder
1413
15−foreign import copyImpl :: Fn2 String String (Promise Unit)
14+foreign import copyImpl ∷ Fn2 String String (Promise Unit)
1615
17−copy :: String -> String -> Aff Unit
18−copy src target = runFn2 copyImpl src target # toAff
16+copy ∷ String -> String -> Aff Unit
17+copy src target = runFn2 copyImpl src target # toAff
server/src/PscIdeClient.purs+16−21
@@ -1,7 +1,6 @@
11module PscIdeClient where
22
33import Prelude
4−
54import Data.Either (Either(..), either)
65import Data.Maybe (Maybe(..))
76import Data.Newtype (class Newtype, un, unwrap)
@@ -22,12 +21,12 @@ import Node.Net.Socket as Socket
2221import Playground.Playground (Folder(..))
2322import Simple.JSON (writeJSON)
2423
25−type BuildCommand
26− = { command ∷ String
27− , params ∷
28− { file ∷ String
29− }
24+type BuildCommand =
25+ { command ∷ String
26+ , params ∷
27+ { file ∷ String
3028 }
29+ }
3130
3231loadCommand ∷
3332 { command ∷ String
@@ -63,17 +62,16 @@ startIdeServer folder port = do
6362 loadPscIde folder port
6463 pure cp
6564 where
66− infoString =
67− " in folder "
68− <> un Folder folder
69− <> " on port "
70− <> show port
65+ infoString =
66+ " in folder "
67+ <> un Folder folder
68+ <> " on port "
69+ <> show port
7170
7271execCommand ∷ Folder -> String -> Aff CP.ExecResult
7372execCommand folder command =
7473 makeAff \callback -> do
75− let
76− options = CP.defaultExecOptions { cwd = Just (un Folder folder) }
74+ let options = CP.defaultExecOptions { cwd = Just (un Folder folder) }
7775 childProcess <- CP.exec command options (callback <<< Right)
7876 pure $ effectCanceler ((log $ "Killing " <> show (CP.pid childProcess)) *> CP.kill SIGKILL childProcess)
7977
@@ -87,22 +85,21 @@ restartIdeServer folder port processRef = do
8785spawnProcess ∷ Folder -> String -> Array String -> Aff ChildProcess
8886spawnProcess folder command args =
8987 makeAff \callback -> do
90− let
91− options = CP.defaultSpawnOptions { cwd = Just (un Folder folder), stdio = Just <$> [Ignore, Ignore, Ignore] }
88+ let options = CP.defaultSpawnOptions { cwd = Just (un Folder folder), stdio = Just <$> [ Ignore, Ignore, Ignore ] }
9289 childProcess <- CP.spawn command args options
9390 callback (Right childProcess)
9491 pure $ effectCanceler
9592 $ (log $ "Killing " <> show (CP.pid childProcess))
9693 *> CP.kill SIGKILL childProcess
9794
98−newtype PscIdeConnection
99− = PscIdeConnection
95+newtype PscIdeConnection = PscIdeConnection
10096 { serverProcessRef ∷ Ref ChildProcess
10197 , port ∷ Int
10298 , folder ∷ Folder
10399 }
104100
105101derive instance ntPscIdeConnection ∷ Newtype PscIdeConnection _
102+
106103instance eqPscIdeConnection ∷ Eq PscIdeConnection where
107104 eq c1 c2 = (unwrap c1).port == (unwrap c2).port
108105
@@ -125,8 +122,7 @@ loadPscIde folder port = do
125122 true -> mempty -- should be covered in onError
126123 false -> do
127124 affCb (Right unit)
128− let
129− command = writeJSON buildCommand <> "\n"
125+ let command = writeJSON buildCommand <> "\n"
130126 Socket.onReady socket (void $ Socket.writeString socket command UTF8 mempty)
131127 pure (closeSocketCanceller socket)
132128
@@ -147,8 +143,7 @@ compileCode code (PscIdeConnection { port, folder, serverProcessRef }) = do
147143 log $ "Enough data on " <> show port <> " ending socket\n"
148144 void $ Socket.endString socket "" UTF8 mempty
149145 affCb (Right newStr)
150− let
151− command = writeJSON buildCommand
146+ let command = writeJSON buildCommand
152147 Socket.onReady socket do
153148 log $ "Socket " <> show port <> " ready"
154149 void $ Socket.writeString socket (command <> "\n") UTF8 mempty