From 30b945d7ef973c0bd6e716f935ab24d4c35b795f Mon Sep 17 00:00:00 2001 From: David Barroso Date: Mon, 30 Dec 2024 15:44:04 +0100 Subject: [PATCH 1/4] asd --- go/api/openapi.yaml | 12 -- go/api/server.gen.go | 133 ++++++------ go/api/types.gen.go | 122 ----------- go/controller/post_signin_webauthn.go | 33 +-- go/controller/post_signin_webauthn_test.go | 180 +++------------- go/controller/post_signin_webauthn_verify.go | 74 ++++++- .../post_signin_webauthn_verify_test.go | 98 +++++++++ go/controller/post_signup_webauthn.go | 9 +- go/controller/post_signup_webauthn_test.go | 27 +-- go/controller/webauthn.go | 74 +++++-- index.html | 194 ++++++++++++++++++ 11 files changed, 565 insertions(+), 391 deletions(-) create mode 100644 index.html diff --git a/go/api/openapi.yaml b/go/api/openapi.yaml index 0f333edc..ab705e5c 100644 --- a/go/api/openapi.yaml +++ b/go/api/openapi.yaml @@ -1043,18 +1043,6 @@ components: example: john.smith@nhost.io format: email type: string - userHandle: - description: User identifier for webauthn - example: 2c35b6f3-c4b9-48e3-978a-d4d0f1d42e24 - pattern: \b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b - type: string - x-go-type-import: - name: uuid - path: "github.com/google/uuid" - x-go-type: uuid.UUID - oneOf: - - required: [email] - - required: [userHandle] SignUpWebauthnRequest: type: object diff --git a/go/api/server.gen.go b/go/api/server.gen.go index df620240..c21c4dec 100644 --- a/go/api/server.gen.go +++ b/go/api/server.gen.go @@ -1797,73 +1797,72 @@ func (sh *strictHandler) GetVersion(ctx *gin.Context) { // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+xc3XfbuJX/V3DY2ZN2S0oe2+1O/LRukm6cZGJt7NQPifcciLwiEZMABwCtqF7973vw", - "QRIUoc9EtibbeZjIIAle3N/9xiUegpgVJaNApQjOHoIMcAJc//wACeEQy3csxpIwqsYSEDEnpfkz+Pjh", - "HZIMcXsjkiwIAw6/VYRDEpxJXkEYiDiDAquHJ4wXWAZnQcVJEAZyVkJwFgjJCU2D+XweBiXmuAC5QMA1", - "++8K+Kz//mvMU5BIkTFhHMkMGlqCMCDqlt/0k2FAcaFexpspV1K6zWvgKy7KXE2eSVmKs+GwmEW4LAcx", - "K4YxlnEW1Xer6cJ1bAiDaxLfgVy2Zn1xyfJkfXHjpdUPtKu4B04ms1cFJvnZV/vfCjKvZyU4pJYcYizb", - "Fy+8blYCYhPNQkPrAL1sngkRZShnNAWOKgHJskUqSlYsqfcOtTxaFcHZpwDUsv6hV6hG1V8vGJ0QXrzI", - "ME31vCSlhI6wEFPGkxyECJRgmj8/gAAZ3Lr80pNEmmvEKopXtg29WrBfcMASRufXH+C3CoRUYzhJiHoY", - "5yPOSuCSgAjOJjgXEAalM/QQwNeScBDnsr/4V+qSJgIlWDZ8GJ1fu4KnLkWSFNCnNAwKkDjBEi8nyiDb", - "cOChBqaYRSVW3FbgReOZGcJlGcU5UVPbd7HxF6U6iimtnH5ylnXbuzV0eSZKRgVsyTSS9Ll18bLLoBbU", - "4/jkL+O/Tk6i+HT8PDr9BU6i5//xC46S0+Ro8nNyegzHp1ospASupvr8efzpKHqOo8ntwy/zz5/HUfPn", - "6Xzpb/epn4/VYz5ESuBCrfE8jkGIa3YHHlt8wCtYwJkoxfatyQf7K84Z3xFyUM96dEQNo5glgGSGJSIJ", - "UEkmBIQWBVyWuVVkZGZozUcCE1zlMuIsh6iohIzGEBEa4TxnU0j0uDIXCRF4nEMSAU1KRqh0xyoBvDY+", - "Ec454GSmJqkE9IaNWdGmcML4mCQJ0AhTRmcFq4Q2kAo+nEcC+D3wqKaY0HuckyQy09Xmy7nArekJg5zF", - "OIeIMlmvwzF4kWQsEhnj0h0kNMrIuIyUnRhjTXfrWRdm0rzqDikTW5VRzRFlMWi90po96h/zWGe1hnhj", - "ZtqlTDiILJJaitpxa/1vvUZOCJxCXzheVwWmaMIJ0CSfGQFA9d2eiYTEshKeea6vR8hctJMogWtnULil", - "wHvKYedrKQytGPuU483N2y1VAuep+qe3DPCO3hmj2R+XM+849Y5Wwjf7wroVYYoM81LzCjWheXzJ4q9g", - "W9d5BzPjDSQU+sdPHCbBWfCHYRsGD62nHir2tl4Lc45nPbr1hD7y3hF6d5Fow7ablyfJEkt/XpY5oIuX", - "qJb3Pg6Mxh7Zfq+GdWSFkkrdi5QiIkJRyVlsAp2+8+HsniTA1zFrVN+3yKFmgrBZko9fv/79/EWG8xxo", - "CiM8yxlOtmSYVfe1otYzCy0Rl283djW1S7h86zUwl5rpok1htlwM7zz4/XOM3tJHDs4bLVz5SaWvKWNp", - "Dl4mfDCG+RuUgDsz9AXazo+urSL8HqKfzop8MngFQthsexvD3o0Ne1A411+ZOPuCdnJyQuVfTz3+KdwQ", - "A22MaruCK5mpoMqGUYyjaQYU2ZnUHSrSenNzwEG3u+qLZN26tc860JXogGqN9f4oPJbblaklErQgHT22", - "rRDw3Yy8aLVj1XpqJfKauiuS0guqKxyjJq/fKRFXU3gcNNLxJzKXXbn4wjI6EAWR2X/SjAk5IMw11vUD", - "fSdcx++ed9XXVCpbEEqKqkAnKM4wx7GupLkEXEl+RFO96j+oQP756f/+mwo28dd3QFOZBWd/OQqDgtD6", - "z5N19qymuSHxdlOO75TTFRO8DntfJKEi9e8mOT9CWMdMfLKWGySlH0sbzDxmOGg4fXk90kJz4Oq5GzO9", - "irSeF6Z6eegckWX/NZcUkCQFIKccsdq4qGnCrTmzk2H5fuZh57ruD1bn27TEZ7nmFNz/pfOWKTcwVqH0", - "xq6GUbicBGefHnxvmYfdYRUXvsY0Ucnb/PbJGOmQ0d9gFMDb+izX229Ty5MD1YIw+BqlLLKDVUWSwceP", - "Fy/d8YgUJePS2TlRtxnysuAsSInMqrHO7U1yPdTX5ytsTisorfHr3tqlq+RMspjlg1E1zkn8FmYvOGg+", - "41rxapldSXg9zxLioxqsYfOjeWLeI35Dz2a2n7rSGjfUr3tuI660zDgXQj3PWs7ukx+O0q3eSO2o4Zo9", - "1G9V0QWb5bB6meH6WP6ekqudQ+EfIClrF7TtPoLey/mg97vcAuWnQO8q65rDbdjW2Xtc71bVw3prTc3Y", - "rXja/aDeBAkRZY5n77XiuQ+8YRlFV0oOukw8OXYdwP98+vy5fHg3V/9/r/9/NUfh4Fl0++effK8zu2Q+", - "rOWURS2yyN7YaRGgXUqOO2gef59N+AnhQhpuaBYEYZDjZsTww+dAHr3SbORu29Dmxw3zXF58u/fWfRKE", - "0cdy3y31h+i+a248ivdmG5pSnOc2St/G32ylH5TEd9RaxrUqeevrzOmJ6kexwebQgpe4xxLzjzz3eoBY", - "d/Qkpo1ps96kPTmJ/nb4Y5mXth+NgLvXPmYsB0zVLd7GpaRuXKqbSQ5z64GI86Z7w7u4H9arlhmj8L4q", - "xkZp+hX99vpq+Pn3CrEWN3ca3XQ1satiXf1ZlNbQdHK5GDeAOrz2r7Ve2O0SS/MSTN8P+SfsFiHEjFIb", - "pGyQT7kJ1JTkORoDIillHJIntQ8/Qm5i+r0u6K8gM+ah6CYjcab3LCJCUaHvQpIh2w/ndu66jWyl26F7", - "uy4j6pCwqqSshE9nr6YheDfhozB9dWAi0m9IWWRRQ/RKtlwBTf7hNDv/QLXa9SxaLTbfVu+gMB0dlNa2", - "/VS+bwbMhx9C/cjaHZ0Q1exCpA1QEBGIMum2hdgm0yYb7zTYf/58Nvj3n9ZWolyOrcdEgPz/LqZ6Fzyu", - "OJGzKzWZWd/fAHPg55WSgwfzmYKOQfRwS2cmZamobG9/lcO9cak9EcmIQHXvMyrwrBYLBPYZVAIviN5L", - "EyFKoASaEJoiRpHpZEYCpCQ0FQP0d8ZRAhKTXCABgOrKRMJiMag5PEwrkoAYKhkb1m+JnLfUn4wsX5vi", - "D6ETZsMHiWPp4B+IqlT5ooupzRvfq5FnAl2ZO1RwpnKepoTSPDFfjDuugN+TGJQynTvKoQIpEoMtBdi3", - "nJc4zgAdD456L5hOpwOsLw8YT4f2WTF8d/Hi1furV9Hx4GiQySLXag28EJcT+2Y7ydlwKKY4TYErVupb", - "hoo9RObNAjWFQRjcAzc7pcHPg6PBkZFcoLgkwVlwoodM/qylaziYQp5Hd5RN6fDL9E4MvggTk6XGuChV", - "057kIgnOgv8CeQN5/lbd/mZ6J94IZtqKTP6upzw+OqohAmq0ue3ZH9bTt98HrenxvQJpsF+UYUClrq+g", - "O5gJRCh6c/MWXYFEVpW1PlVFgfnMUN65f8I4enNzjdwvgzyThIHEqWhbidWkwwxwLrN/ruLSa3vLHnnj", - "dMN6+FOLLhHIkDtbYIihEMUZxHfOMs3Nwe081B8b9hf3GnCyenXfmRDF8ZzQuyFJZL3pXjLh4fuICWka", - "u2XT76Zdyt9YMvtufPe0js+7Jl6lMPMnQ/5CF9fkDCmmQYJEpXf4J1WezzpORte4fP7ik95wbhFSK0bY", - "xAo4jllFJZoSmZnYwnYMPRPNtUroLlOKSNK2S1lYFVE6NzUXDLwllqtRHWG5JzR7H/s9Mpb9D+d8yuxA", - "iGw5AGE0si0cyPRw2EbrnSA2ZCybE/1xdH79JwdFBZiBznyPOVzI/1aCeaUf6exB7gncFa2kjwzzqhbL", - "dYArFquInQ7Q+yrPke2FQgVgKtD15fVIJRqmo1LH8gAJJAtW9so2HWq91WghTJNOr5fF1iDafutGkxbX", - "DuYb2WMD9n4tsrfv87EB7rZNb4zpKpgIXWJEG4hIw9fF7z1coJgsh02atA6qS2naA/aK1WLn6EE5UKVQ", - "Aqi0VS7l2YzC9D2pHzeM2GIvpXfCAbpwsvCEgaDPJIKvRMgQEdmUOq3FHyAV9No0FJmsUem7GcFGoWNM", - "1SO631gyFDM6IWnFoXnPM2G+9jCykaKqRBhRmOqLA3Shp7TF1U6VwH70augTA584mi9HA9Ng6hfBoTnA", - "YBtJbA4E2L88djdJn8RHLGmX9cjprzglsY7ythZXMzlSkq6EhoOsOEW4cS1kYsonNi0Szlzb4e6WgTe3", - "Qb1u072Cv7S39aCskqZsN7Q7xqlYLTU/rk3q7kh0hVRuJpVyv3L4ZEnIPoKXzRKJBqZeRtE0Em+AzE3b", - "dLw/eBZbop7EO/R6kTxYNZ9XaQX3YFRD5PRq9/BornlB2cKLd/uPHgWhp/Xi63XJqonjU73euWY2akBZ", - "jlJoD4Zy4KrKrbPyqnysrHxJD/Jh2z0OKRESuHJ7nkzc+OFuQVkgu3USzMPg9Ojku5HePXlniZRVJSog", - "zjAlolC0NEe6aGKePx4x+ksRbXNScg+0LkB0vLfHUFXlhvWKqlxXr6jKLTxKVT6CR+k32T6BR/F0t+7i", - "UWqglngUDY/HozigbOxRqvLRPMqy5tnD9ChVuZ1H0aAsR2nBo2xQ6rveY4nPd1bJ08CwgZfQpKpcx5yi", - "gczZDLaK1wWmOafCd2sLj+yc8tAcpfVlWgfNKhsaJm1X3mqkFlr49oTZkkbBucXtSbJo7YgcPm2+Pdbf", - "M3HWhjBFzUlotqmGIpzopgnd4kBT68UYNz/+3DbldPorlCTEGRNAF89pMb13A3RDdOhBE4RNLs0Lex6e", - "foHNj22fBhFtvq2z74QhwRzRag9wcyTJlOpic+jmWlFyGvL2KEqetr8nFSVTkDE8QnbRKjI8r4GwteBO", - "QKjrLhkWaAxAmwKMwovCVAkMByF23MIzlGjha5qYLMjuoYZ9nJUsdY5IjTao1K1uOdy3HKzsczzAul1H", - "CGzBToG/qmqnNByWPL0BtpulfW4X3h5B8yR6TwdJTYxV3h1bJMKH5fpX896UR/VQUQmJxtDtslTewDZQ", - "INyeh7wA7UI600F3qLs8N8fYHJH8GEA7HZ2Hgbbph20sdS8S0+MIt3t2+oFNDHpdFHftua1r1yZ9Haht", - "+rOspaxJd9xj2Jd8pdbeMnSPK5+HG97eHhu+wSOLB8ErtehgfXJ07DuhzR5Lz9uP/5zj7d1j7X3vt7cO", - "e8fgG1nrpT5Gt0TTPTOeeWxraMfcrYLFKOyP7QbKn0Ktvx1xcYDu5E9NW+ZKgAX59n7KLXqnHaLa/uij", - "wdHgKErgfm2Hd/24p5W5p4V2cfWHccL2t/abNO8bLjh8NK9R0P5fAAAA//8eahZ6EmEAAA==", + "H4sIAAAAAAAC/+xcW3fbOJL+Kzic3pOZHVJy257Zjp/Wk85sO0nH2liZPCTecyCyRCImATYAWtFk9d/3", + "4EISFKFrLFudnX7oyCAAFuqrC6pQxNcgZkXJKFApgouvQQY4Aa5/voOEcIjlGxZjSRhVbQmImJPS/Bm8", + "f/cGSYa47YgkC8KAw28V4ZAEF5JXEAYizqDAavCU8QLL4CKoOAnCQM5LCC4CITmhabBYLMKgxBwXIJcI", + "GLP/roDP++8fY56CRIqMKeNIZtDQEoQBUV1+0yPDgOJCvYw3U66ldJfXwBdclLmaPJOyFBfDYTGPcFkO", + "YlYMYyzjLKp7q+nCTWwIgzGJ70CuWrN+uGJ5sn649dLqAe0q7oGT6fxlgUl+8cX+t4bM8bwEh9SSQ4xl", + "++Kl181LQGyqWWhoHaCfmzEhogzljKbAUSUgWbVIRcmaJfXeoZZHqyK4+BiAWtY/9ApVq/rrBaNTwosX", + "GaapnpeklNARFmLGeJKDEIESTPPnOxAgg1uXX3qSSHONWEXxyrahVwv2Cw5Ywuhy/A5+q0BI1YaThKjB", + "OB9xVgKXBERwMcW5gDAonaavAXwpCQdxKfuLf6keaSJQgmXDh9Hl2BU89SiSpIA+pWFQgMQJlng1UQbZ", + "hgNfa2CKeVRixW0FXjSZmyZcllGcEzW1fRebfFaqo5jSyulHZ1m3va6hyzNRMipgR6aRpM+tq5+7DGpB", + "PY3P/jL56/Qsis8nz6Pzn+Asev4fP+EoOU9Opj8m56dweq7FQkrgaqpPnyYfT6LnOJrefv1p8enTJGr+", + "PF+s/O2O+vFUDfMhUgIXao2XcQxCjNkdeGzxEa9gCWeiFNu3Jh/sLzlnfE/IQY316IhqRjFLAMkMS0QS", + "oJJMCQgtCrgsc6vIyMzQmo8EprjKZcRZDlFRCRlNICI0wnnOZpDodmUuEiLwJIckApqUjFDptlUCeG18", + "IpxzwMlcTVIJ6DUbs6JN4ZTxCUkSoBGmjM4LVgltIBV8OI8E8HvgUU0xofc4J0lkpqvNl/OAW9MTBjmL", + "cQ4RZbJeh2PwIslYJDLGpdtIaJSRSRkpOzHBmu7Wsy7NpHnVbVImtiqjmiPKYtB6pTV71D9mWGe1hnhj", + "ZtqlTDmILJJaitp2a/1vvUZOCJxCXzh+qQpM0ZQToEk+NwKA6t6eiYTEshKeecbjETIP7SRK4NoZFG4p", + "8J5y2PlaCkMrxj7lePXh9Y4qgfNU/dNbBnhb74zR7LfLubedelsr4Zt9ad2KMEWGeal5hZrQDF+x+BvY", + "1XXewdx4AwmF/vEDh2lwEfxh2G6Dh9ZTDxV7W6+FOcfzHt16Qh95bwi9u0q0YdvPy5NkhaW/LMsc0NXP", + "qJb3Pg6Mxh7Zfqua9c4KJZXqi5QiIkJRyVlsNjp958PZPUmAb2LWqO63zKFmgrBZko9fv/798kWG8xxo", + "CiM8zxlOdmSYVfeNotYzCy0R16+3djW1S7h+7TUw15rpog1hdlwM7wx8+Bijt/SRg/NWC1d+Uulrylia", + "g5cJ74xh/gYl4M4MfYG286OxVYTfw+6nsyKfDN6AEDba3sWwd/eGPSic5y/NPvuKdmJyQuVfzz3+KdwS", + "A22MaruCK5mpTZXdRjGOZhlQZGdSPdRO69WHI950u6u+SjatW/usI12J3lBtsN7vhcdyuzK1QoKWpKPH", + "tjUCvp+RF612rFtPrUReU3dDUnpFdYZj1MT1ewXiagqPg0Z6/4nMY1cuPrOMDkRBZPafNGNCDghzjXU9", + "oO+E6/275131MxXKFoSSoirQGYozzHGsM2kuATeSn9BUr/oPaiP//Px//01tNvGXN0BTmQUXfzkJg4LQ", + "+s+zTfasprkh8XZbju8V0xVTvAl7305C7dQfTHK+h20dM/uTjdwgKX1f2s3MY24HDaevxyMtNEeunvsx", + "06tIm3lhspfHzhFZ9l9zTQFJUgBy0hHrjYuaJtyZM3sZloczD3vndb+zPN+2KT7LNSfh/i+dt0z5ABO1", + "laZHzYvFFuS3KtntGgZfopRFtrHkTLKY5YNRNclJ/BrmLzjo7CyuxaHmpDMwIkXJuHQOIup5jNxnwUWQ", + "EplVEx0upyyaWbqGzY9mxKJH/Jb21hyKdAGIG+o3jduKKy0zLoVQ41nL2UPyw5Gj9cd7HcnacLL3AFLn", + "apLD6lXq9L78PW35996gfQehQrugXbPb+oThnT6FcdNmHwN91qkj4duwzf72uN7N9Yb1gY+asZuHs6cU", + "vQkSIsocz99qxXMHvGIZRTdKDrpMPDt1nfP/fPz0qfz6ZqH+/1b//2aBwsGz6PbPP/heZ85ufFjLGYta", + "ZJHt2Dm4pl1KTjtonj7M0fCUcCENNzQLgjDIcdNi+OFzII+e/zRy97twuI+y+XB58e3eW5/eE0Yfy323", + "1B+j+6658Sjem21pSnGeX0+Di4+7+Zud9IOS+I5ay7hRJW999SI9UX0vtjiyWPIS91hi/p7nXg8Q6zqT", + "xBTXbFcxcyAn0T+kfSzz0lZJEXBPgCeM5YCp6uItp0nqcpq6xOE4E+JEXDY1Bd7FfbdetcwYhbdVMTFK", + "088zt8/Xw88faou1fOTQ6KariV0V6+rPsrSGpr7IxbgB1OG1f631wm5XWJqfwVSjkH/CfjuEmFFqNylb", + "xFNuADUjeY4mgEhKGYfkSe3D9xCbmCqkK/oryIx5KPqQkTjTmfSIUFToXkgyZKu03HpSt7yqdOtGbzdF", + "RB0S1iU6lfDp6NWUqe4nfBRmL49MRPplEsssaohey5YboMk/nBLc7yiDuJlF68Xm2/IdFGajo9LatsrH", + "V8luPkcQ6kfWnjOEqGYXIu0GBRGBKJNusYItfWyi8U7Z96dPF4N//2FjJsrl2GZMBMj/72Kqz2bjihM5", + "v1GTmfX9DTAHflkpOfhqiuf1HkQ3t3RmUpaKyrb7yxzujUvtiUhGBKorclGB57VYILBjUAm8IPqER4Qo", + "gRJoQmiKGEWmvhYJkJLQVAzQ3xlHCUhMcoEEAKozEwmLxaDm8DCtSAJiqGRsWL8lct5Sf8iwem2KP4RO", + "md0+SBxLB/9AVKWKF11Mbdz4VrU8E+jG9FCbMxXzNCmUZsRied9xA/yexKCU6dJRDrWRIjHYVIB9y2WJ", + "4wzQ6eCk94LZbDbA+vGA8XRox4rhm6sXL9/evIxOByeDTBa5Vmvghbie2jfbSS6GQzHDaQpcsVJ3GSr2", + "EJk3C9QUBmFwD9yc3wU/Dk4GJ0ZygeKSBBfBmW4y8bOWruFgBnke3VE2o8PPszsx+CzMniw1xkWpmvYk", + "V0lwEfwXyA+Q569V91ezO/FKMFPsYuJ3PeXpyUkNEVCjzW0l+bCevv1qZUPl6Q1Ig/2yDAMqdX4F3cFc", + "IELRqw+v0Q1IZFVZ61NVFJjPDeWd/lPG0asPY+R+r+KZJAwkTkVb4KomHWaAc5n9cx2XfrFdDsgbp0bT", + "w59adIlAhtz5EkMMhSjOIL5zlmk6B7eLUH8C11/cL4CT9at7YEIUx3NC74YkkfVRcMmEh+8jJqQpN5ZN", + "FZZ2KX9jyfzB+O4paF50TbwKYRZPhvyVTq7JOVJMgwSJSp87T6s8n3ecjM5x+fzFx9vFrYuQWjHCZq+A", + "45hVVKIZkZnZW9g6lmeieVYJXftIEUnaIh4LqyJKx6bmgYG3xHI9qiMsD4Rm7xO0R8ay/zmXT5kdCJFN", + "ByCMRrawAJnKAlv+uxfEhoxVc6I/ji7Hf3JQVIAZ6MxXgsOl+G8tmDd6SOcM8kDgrilwfGSY1xX+bQJc", + "sVjt2OkAva3yHNkKHVQApgKNr8cjFWiYOj+9lwdIIFmysje2FE7rrUYLYZp0KpAstgbR9gssmrS4djDf", + "yh4bsA9rkb3ViI8NcLeYd2tM18FE6Aoj2kBEGr4uf4XgAsVkOWzCpE1QXUtTHnBQrJbrGY/KgSqFEkCl", + "zXIpz2YUpu9J/bhhxJYr/LwTDtCVE4UnDAR9JhF8IUKGiMgm1Wkt/gCpTa8NQ5GJGpW+mxZsFDrGVA3R", + "VbCSoZjRKUkrDs17ngnzDYKRjRRVJcKIwkw/HKArPaVNrnayBPZTTEOfGPjE0XzPGJiyR78IDs1n9btI", + "YvOZ+uHlsXtI+iQ+YkURp0dOf8UpifUub2dxNZMjJelKaDjIilOEG9dCpiZ9YsMi4cy1G+5uGnh7G9Sr", + "gTwo+CsrLo/KKmnK9kO7Y5yK9VLz/dqk7olEV0jldlIpDyuHTxaEHGLzsl0g0cDUiyjqIo5tkKlrXA4K", + "z3JJ1JN4h14tkger5qMfreAejGqIZi3beng0z7yg7ODFu/VHj4LQ03rxzbpk1cTxqV7vXDMbNaCsRim0", + "1xU5cFXlzlF5VT5WVL6iBvm47R6HlAgJXLk9TyRu/HA3oSyQPToJFmFwfnL2YKR374NZIWVViQqIM0yJ", + "KBQtzUUjmpjnj0fMe+Wstc1JyT3QOgHR8d4eQ1WVW+YrqnJTvqIqd/AoVfkIHqVfZPsEHsVT3bqPR6mB", + "WuFRNDwej+KAsrVHqcpH8yirimeP06NU5W4eRYOyGqUlj7JFqm98wBSf7waNp4FhCy+hSVWxjrnbAZkb", + "A2wWrwtMc3uCr2sLj+zcPdBc8PR5Vm+aVTQ0TNqqvPVILZXwHQizFYWCC4vbk0TR2hE5fNr+eKx/ZuKs", + "DWGKmvu5bFENRTjRRRO6xIGm1osxbn78uS3K6dRXKEmIMyaALt8eYmrvBugD0VsPmiBsYmle2Fva9Ats", + "fGzrNIho420dfScMCeaIVnutmCNJJlUXm6sgN4qSU5B3QFHylP09qSiZhIzhEbKLVjvDyxoImwvubAh1", + "3iXDAk0AaJOAUXhRmCmB4SDEnkd4hhItfE0RkwXZvWqvj7OSpc7FndEWmbr1JYeHloO1dY5HmLfrCIFN", + "2Cnw12XtlIbDitFbYLtd2OdW4R0QNE+g93SQ1MRY5d2zRCL8ulr/at6b9KhuKioh0QS6VZbKG9gCCoTb", + "W3qXoF0KZzroDnWV5/YYm4t7HwNop6LzONA29bCNpe7txHQ7wu2ZnR6wjUGvk+KuPbd57dqkbwK1DX9W", + "lZQ14Y57OfiKr9TaLkP3Eu1FuGX39jLrLYYsX0+u1KKD9dnJqe/eMHtZOm8//nMuXXcvW/e933Yd9i5n", + "N7LWC32MbommemYy99jW0La5RwXLu7A/tgcofwq1/nbExQG6Ez81ZZlrARbk2+spd6iddohq66NPBieD", + "kyiB+40V3vVwTylzTwvt4uoP44Stb+0Xad43XHD4aF6joP2/AAAA//80s8RvqF8AAA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/go/api/types.gen.go b/go/api/types.gen.go index f4d137fa..15655cb3 100644 --- a/go/api/types.gen.go +++ b/go/api/types.gen.go @@ -9,8 +9,6 @@ import ( "time" protocol "github.com/go-webauthn/webauthn/protocol" - uuid "github.com/google/uuid" - "github.com/oapi-codegen/runtime" openapi_types "github.com/oapi-codegen/runtime/types" ) @@ -236,18 +234,8 @@ type SignInPasswordlessEmailRequest struct { type SignInWebauthnRequest struct { // Email A valid email Email *openapi_types.Email `json:"email,omitempty"` - - // UserHandle User identifier for webauthn - UserHandle *uuid.UUID `json:"userHandle,omitempty"` - union json.RawMessage } -// SignInWebauthnRequest0 defines model for . -type SignInWebauthnRequest0 = interface{} - -// SignInWebauthnRequest1 defines model for . -type SignInWebauthnRequest1 = interface{} - // SignInWebauthnResponse defines model for SignInWebauthnResponse. type SignInWebauthnResponse = protocol.PublicKeyCredentialRequestOptions @@ -627,113 +615,3 @@ func (a SignUpWebauthnVerifyRequest) MarshalJSON() ([]byte, error) { } return json.Marshal(object) } - -// AsSignInWebauthnRequest0 returns the union data inside the SignInWebauthnRequest as a SignInWebauthnRequest0 -func (t SignInWebauthnRequest) AsSignInWebauthnRequest0() (SignInWebauthnRequest0, error) { - var body SignInWebauthnRequest0 - err := json.Unmarshal(t.union, &body) - return body, err -} - -// FromSignInWebauthnRequest0 overwrites any union data inside the SignInWebauthnRequest as the provided SignInWebauthnRequest0 -func (t *SignInWebauthnRequest) FromSignInWebauthnRequest0(v SignInWebauthnRequest0) error { - b, err := json.Marshal(v) - t.union = b - return err -} - -// MergeSignInWebauthnRequest0 performs a merge with any union data inside the SignInWebauthnRequest, using the provided SignInWebauthnRequest0 -func (t *SignInWebauthnRequest) MergeSignInWebauthnRequest0(v SignInWebauthnRequest0) error { - b, err := json.Marshal(v) - if err != nil { - return err - } - - merged, err := runtime.JSONMerge(t.union, b) - t.union = merged - return err -} - -// AsSignInWebauthnRequest1 returns the union data inside the SignInWebauthnRequest as a SignInWebauthnRequest1 -func (t SignInWebauthnRequest) AsSignInWebauthnRequest1() (SignInWebauthnRequest1, error) { - var body SignInWebauthnRequest1 - err := json.Unmarshal(t.union, &body) - return body, err -} - -// FromSignInWebauthnRequest1 overwrites any union data inside the SignInWebauthnRequest as the provided SignInWebauthnRequest1 -func (t *SignInWebauthnRequest) FromSignInWebauthnRequest1(v SignInWebauthnRequest1) error { - b, err := json.Marshal(v) - t.union = b - return err -} - -// MergeSignInWebauthnRequest1 performs a merge with any union data inside the SignInWebauthnRequest, using the provided SignInWebauthnRequest1 -func (t *SignInWebauthnRequest) MergeSignInWebauthnRequest1(v SignInWebauthnRequest1) error { - b, err := json.Marshal(v) - if err != nil { - return err - } - - merged, err := runtime.JSONMerge(t.union, b) - t.union = merged - return err -} - -func (t SignInWebauthnRequest) MarshalJSON() ([]byte, error) { - b, err := t.union.MarshalJSON() - if err != nil { - return nil, err - } - object := make(map[string]json.RawMessage) - if t.union != nil { - err = json.Unmarshal(b, &object) - if err != nil { - return nil, err - } - } - - if t.Email != nil { - object["email"], err = json.Marshal(t.Email) - if err != nil { - return nil, fmt.Errorf("error marshaling 'email': %w", err) - } - } - - if t.UserHandle != nil { - object["userHandle"], err = json.Marshal(t.UserHandle) - if err != nil { - return nil, fmt.Errorf("error marshaling 'userHandle': %w", err) - } - } - b, err = json.Marshal(object) - return b, err -} - -func (t *SignInWebauthnRequest) UnmarshalJSON(b []byte) error { - err := t.union.UnmarshalJSON(b) - if err != nil { - return err - } - object := make(map[string]json.RawMessage) - err = json.Unmarshal(b, &object) - if err != nil { - return err - } - - if raw, found := object["email"]; found { - err = json.Unmarshal(raw, &t.Email) - if err != nil { - return fmt.Errorf("error reading 'email': %w", err) - } - } - - if raw, found := object["userHandle"]; found { - err = json.Unmarshal(raw, &t.UserHandle) - if err != nil { - return fmt.Errorf("error reading 'userHandle': %w", err) - } - } - - return err -} diff --git a/go/controller/post_signin_webauthn.go b/go/controller/post_signin_webauthn.go index 9cfdc9ce..e12cf21c 100644 --- a/go/controller/post_signin_webauthn.go +++ b/go/controller/post_signin_webauthn.go @@ -38,6 +38,17 @@ func webauthnCredentials( return creds, nil } +func (ctrl *Controller) postSigninWebauthnDiscoverableLogin( //nolint:ireturn + logger *slog.Logger, +) (api.PostSigninWebauthnResponseObject, error) { + creation, apiErr := ctrl.Webauthn.BeginDiscoverableLogin(logger) + if apiErr != nil { + return ctrl.sendError(apiErr), nil + } + + return api.PostSigninWebauthn200JSONResponse(creation.Response), nil +} + func (ctrl *Controller) PostSigninWebauthn( //nolint:ireturn ctx context.Context, request api.PostSigninWebauthnRequestObject, @@ -49,16 +60,11 @@ func (ctrl *Controller) PostSigninWebauthn( //nolint:ireturn return ctrl.sendError(ErrDisabledEndpoint), nil } - var user sql.AuthUser - var apiErr *APIError - switch { - case request.Body.UserHandle != nil: - user, apiErr = ctrl.wf.GetUser(ctx, *request.Body.UserHandle, logger) - case request.Body.Email != nil: - user, apiErr = ctrl.wf.GetUserByEmail(ctx, string(*request.Body.Email), logger) - default: - return ctrl.sendError(ErrInvalidRequest), nil + if request.Body.Email == nil { + return ctrl.postSigninWebauthnDiscoverableLogin(logger) } + + user, apiErr := ctrl.wf.GetUserByEmail(ctx, string(*request.Body.Email), logger) if apiErr != nil { return ctrl.sendError(apiErr), nil } @@ -74,10 +80,11 @@ func (ctrl *Controller) PostSigninWebauthn( //nolint:ireturn } waUser := WebauthnUser{ - ID: user.ID, - Name: user.DisplayName, - Email: user.Email.String, - Credentials: creds, + ID: user.ID, + Name: user.DisplayName, + Email: user.Email.String, + Credentials: creds, + Discoverable: false, } creation, apiErr := ctrl.Webauthn.BeginLogin(waUser, logger) diff --git a/go/controller/post_signin_webauthn_test.go b/go/controller/post_signin_webauthn_test.go index 208af1e5..6c649202 100644 --- a/go/controller/post_signin_webauthn_test.go +++ b/go/controller/post_signin_webauthn_test.go @@ -15,7 +15,7 @@ import ( "go.uber.org/mock/gomock" ) -func TestPostSigninWebauthn(t *testing.T) { //nolint:maintidx +func TestPostSigninWebauthn(t *testing.T) { t.Parallel() userID := uuid.MustParse("DB477732-48FA-4289-B694-2886A646B6EB") @@ -27,145 +27,6 @@ func TestPostSigninWebauthn(t *testing.T) { //nolint:maintidx } cases := []testRequest[api.PostSigninWebauthnRequestObject, api.PostSigninWebauthnResponseObject]{ - { - name: "success with userHandle", - config: getConfig, - db: func(ctrl *gomock.Controller) controller.DBClient { - mock := mock.NewMockDBClient(ctrl) - - mock.EXPECT().GetUser( - gomock.Any(), userID, - ).Return(getSigninUser(userID), nil) - - mock.EXPECT().GetSecurityKeys( - gomock.Any(), - userID, - ).Return( - []sql.AuthUserSecurityKey{ - { - ID: uuid.MustParse( - "307b758d-c0b0-4ce3-894b-f8ddec753c29", - ), - UserID: uuid.MustParse( - "53b008ee-bafb-489c-bcea-9237e0b778a7", - ), - CredentialID: "EuKJAraRGDcmHon-EjDoqoU5Yvk", - CredentialPublicKey: []byte{ - 165, - 1, - 2, - 3, - 38, - 32, - 1, - 33, - 88, - 32, - 252, - 177, - 134, - 121, - 67, - 213, - 214, - 63, - 237, - 6, - 140, - 235, - 18, - 28, - 108, - 116, - 46, - 248, - 172, - 201, - 3, - 152, - 183, - 242, - 236, - 130, - 102, - 174, - 113, - 76, - 228, - 14, - 34, - 88, - 32, - 229, - 226, - 168, - 14, - 4, - 158, - 235, - 9, - 15, - 249, - 188, - 47, - 65, - 250, - 174, - 87, - 241, - 33, - 146, - 18, - 223, - 140, - 90, - 111, - 3, - 45, - 151, - 11, - 228, - 58, - 46, - 81, - }, - Counter: 0, - Transports: "", - Nickname: sql.Text(""), - }, - }, - nil, - ) - - return mock - }, - request: api.PostSigninWebauthnRequestObject{ - Body: &api.PostSigninWebauthnJSONRequestBody{ - Email: nil, - UserHandle: ptr(userID), - }, - }, - expectedResponse: api.PostSigninWebauthn200JSONResponse( - protocol.PublicKeyCredentialRequestOptions{ - Challenge: protocol.URLEncodedBase64("ignoreme"), - Timeout: 60000, - RelyingPartyID: "react-apollo.example.nhost.io", - AllowedCredentials: []protocol.CredentialDescriptor{ - { //nolint:exhaustruct - Type: "public-key", - CredentialID: credentialID, - }, - }, - UserVerification: "preferred", - Hints: nil, - Extensions: nil, - }, - ), - expectedJWT: nil, - jwtTokenFn: nil, - getControllerOpts: []getControllerOptsFunc{}, - }, - { name: "success with email", config: getConfig, @@ -280,8 +141,7 @@ func TestPostSigninWebauthn(t *testing.T) { //nolint:maintidx }, request: api.PostSigninWebauthnRequestObject{ Body: &api.PostSigninWebauthnJSONRequestBody{ - Email: ptr(types.Email("jane@acme.com")), - UserHandle: nil, + Email: ptr(types.Email("jane@acme.com")), }, }, expectedResponse: api.PostSigninWebauthn200JSONResponse( @@ -304,6 +164,7 @@ func TestPostSigninWebauthn(t *testing.T) { //nolint:maintidx jwtTokenFn: nil, getControllerOpts: []getControllerOptsFunc{}, }, + { name: "user disabled", config: getConfig, @@ -321,8 +182,7 @@ func TestPostSigninWebauthn(t *testing.T) { //nolint:maintidx }, request: api.PostSigninWebauthnRequestObject{ Body: &api.PostSigninWebauthnJSONRequestBody{ - Email: ptr(types.Email("jane@acme.com")), - UserHandle: nil, + Email: ptr(types.Email("jane@acme.com")), }, }, expectedResponse: controller.ErrorResponse{ @@ -348,8 +208,7 @@ func TestPostSigninWebauthn(t *testing.T) { //nolint:maintidx }, request: api.PostSigninWebauthnRequestObject{ Body: &api.PostSigninWebauthnJSONRequestBody{ - Email: ptr(types.Email("jane@acme.com")), - UserHandle: nil, + Email: ptr(types.Email("jane@acme.com")), }, }, expectedResponse: controller.ErrorResponse{ @@ -361,6 +220,35 @@ func TestPostSigninWebauthn(t *testing.T) { //nolint:maintidx jwtTokenFn: nil, getControllerOpts: []getControllerOptsFunc{}, }, + + { + name: "success discoverable login", + config: getConfig, + db: func(ctrl *gomock.Controller) controller.DBClient { + mock := mock.NewMockDBClient(ctrl) + + return mock + }, + request: api.PostSigninWebauthnRequestObject{ + Body: &api.PostSigninWebauthnJSONRequestBody{ + Email: nil, + }, + }, + expectedResponse: api.PostSigninWebauthn200JSONResponse( + protocol.PublicKeyCredentialRequestOptions{ + Challenge: protocol.URLEncodedBase64("ignoreme"), + Timeout: 60000, + RelyingPartyID: "react-apollo.example.nhost.io", + AllowedCredentials: nil, + UserVerification: "preferred", + Hints: nil, + Extensions: nil, + }, + ), + expectedJWT: nil, + jwtTokenFn: nil, + getControllerOpts: []getControllerOptsFunc{}, + }, } for _, tc := range cases { diff --git a/go/controller/post_signin_webauthn_verify.go b/go/controller/post_signin_webauthn_verify.go index b9855b49..47bd4dc3 100644 --- a/go/controller/post_signin_webauthn_verify.go +++ b/go/controller/post_signin_webauthn_verify.go @@ -1,12 +1,69 @@ package controller import ( + "bytes" "context" + "encoding/json" + "fmt" + "log/slog" + "github.com/go-webauthn/webauthn/protocol" + "github.com/go-webauthn/webauthn/webauthn" + "github.com/google/uuid" "github.com/nhost/hasura-auth/go/api" "github.com/nhost/hasura-auth/go/middleware" ) +func (ctrl *Controller) PostSigninWebauthnVerifyUserHandle( + ctx context.Context, + response *protocol.ParsedCredentialAssertionData, + logger *slog.Logger, +) webauthn.DiscoverableUserHandler { + return func(_, userHandle []byte) (webauthn.User, error) { + // we need to encoide it back because the client treats it as a string including the hyphens + b, err := json.Marshal(protocol.URLEncodedBase64(userHandle)) + if err != nil { + return nil, fmt.Errorf("failed to marshal user handle: %w", err) + } + userID, err := uuid.Parse(string(b)) + if err != nil { + return nil, fmt.Errorf("failed to parse user ID: %w", err) + } + + keys, apiErr := ctrl.wf.GetUserSecurityKeys(ctx, userID, logger) + if apiErr != nil { + return nil, apiErr + } + + creds, apiErr := webauthnCredentials(keys, logger) + if apiErr != nil { + return nil, apiErr + } + + for i, userCreds := range creds { + if bytes.Equal(response.RawID, userCreds.ID) { + userCreds.Flags = webauthn.CredentialFlags{ + UserPresent: response.Response.AuthenticatorData.Flags.UserPresent(), + UserVerified: response.Response.AuthenticatorData.Flags.UserVerified(), + BackupEligible: response.Response.AuthenticatorData.Flags.HasBackupEligible(), + BackupState: response.Response.AuthenticatorData.Flags.HasBackupState(), + } + creds[i] = userCreds + } + } + fmt.Println(userID) + + return WebauthnUser{ + ID: userID, + Name: "", + Email: "", + Credentials: creds, + Discoverable: true, + UserHandle: userHandle, + }, nil + } +} + func (ctrl *Controller) PostSigninWebauthnVerify( //nolint:ireturn ctx context.Context, request api.PostSigninWebauthnVerifyRequestObject, @@ -24,12 +81,25 @@ func (ctrl *Controller) PostSigninWebauthnVerify( //nolint:ireturn return ctrl.sendError(ErrInvalidRequest), nil } - _, webauthnUser, apiErr := ctrl.Webauthn.FinishLogin(credData, logger) + _, _, apiErr := ctrl.Webauthn.FinishLogin( + credData, + ctrl.PostSigninWebauthnVerifyUserHandle(ctx, credData, logger), + logger, + ) if apiErr != nil { return ctrl.sendError(apiErr), nil } - user, apiErr := ctrl.wf.GetUser(ctx, webauthnUser.ID, logger) + b, err := json.Marshal(protocol.URLEncodedBase64(credData.Response.UserHandle)) + if err != nil { + return nil, fmt.Errorf("failed to marshal user handle: %w", err) + } + userID, err := uuid.Parse(string(b)) + if err != nil { + return nil, fmt.Errorf("failed to parse user ID: %w", err) + } + + user, apiErr := ctrl.wf.GetUser(ctx, userID, logger) if apiErr != nil { return ctrl.sendError(apiErr), nil } diff --git a/go/controller/post_signin_webauthn_verify_test.go b/go/controller/post_signin_webauthn_verify_test.go index 6538441b..04bf3396 100644 --- a/go/controller/post_signin_webauthn_verify_test.go +++ b/go/controller/post_signin_webauthn_verify_test.go @@ -236,6 +236,104 @@ func TestPostSigninWebauthnVerify(t *testing.T) { //nolint:maintidx jwtTokenFn: nil, getControllerOpts: []getControllerOptsFunc{}, }, + + { + name: "user discoverable", + config: func() *controller.Config { + config := getConfig() + config.WebauthnRPOrigins = []string{"http://localhost:3000"} + config.WebauthnRPID = "localhost" + config.WebauthnRPName = "React pollo Example" + + return config + }, + db: func(ctrl *gomock.Controller) controller.DBClient { + mock := mock.NewMockDBClient(ctrl) + + mock.EXPECT().GetUser( + gomock.Any(), userID, + ).Return(getSigninUser(userID), nil) + + mock.EXPECT().GetUserRoles( + gomock.Any(), userID, + ).Return([]sql.AuthUserRole{ + {UserID: userID, Role: "user"}, //nolint:exhaustruct + {UserID: userID, Role: "me"}, //nolint:exhaustruct + }, nil) + + mock.EXPECT().InsertRefreshtoken( + gomock.Any(), + cmpDBParams(sql.InsertRefreshtokenParams{ + UserID: userID, + RefreshTokenHash: pgtype.Text{}, //nolint:exhaustruct + ExpiresAt: sql.TimestampTz(time.Now().Add(30 * 24 * time.Hour)), + Type: sql.RefreshTokenTypeRegular, + Metadata: nil, + }), + ).Return(refreshTokenID, nil) + + mock.EXPECT().UpdateUserLastSeen( + gomock.Any(), userID, + ).Return(sql.TimestampTz(time.Now()), nil) + + return mock + }, + request: api.PostSigninWebauthnVerifyRequestObject{ + Body: unmarshalRequest( + t, + []byte( + `{"email":"whasd@asd.com","credential":{"id":"rkT-z-JhiBWGseoxXEKPulXcKcM","rawId":"rkT-z-JhiBWGseoxXEKPulXcKcM","response":{"authenticatorData":"SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MdAAAAAA","clientDataJSON":"eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoibk02b204bHp2VDVveHZSQ0Z1QXFSRE9qLXRsQXE4RmRQLWVSTk93c2ZncyIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCJ9","signature":"MEYCIQDAjwCZjJdHQub-tZHyXKLYdm4_IYefv2p-V8Z5k8a9lwIhAOhV5Kc5po30xgAc3XrzSiwy-Q5ItdcIMXPP5-4FvHOt","userHandle":"d0902ee3-d160-4853-af6a-8d4b6248117e"},"type":"public-key","clientExtensionResults":{},"authenticatorAttachment":"platform"}}`, //nolint:lll + ), + ), + }, + expectedResponse: api.PostSigninWebauthnVerify200JSONResponse{ + Session: &api.Session{ + AccessToken: "", + AccessTokenExpiresIn: 900, + RefreshTokenId: "c3b747ef-76a9-4c56-8091-ed3e6b8afb2c", + RefreshToken: "1fb17604-86c7-444e-b337-09a644465f2d", + User: &api.User{ + AvatarUrl: "", + CreatedAt: time.Now(), + DefaultRole: "user", + DisplayName: "Jane Doe", + Email: ptr(types.Email("jane@acme.com")), + EmailVerified: true, + Id: "d0902ee3-d160-4853-af6a-8d4b6248117e", + IsAnonymous: false, + Locale: "en", + Metadata: map[string]any{}, + PhoneNumber: nil, + PhoneNumberVerified: false, + Roles: []string{"user", "me"}, + }, + }, + }, + expectedJWT: &jwt.Token{ + Raw: "", + Method: jwt.SigningMethodHS256, + Header: map[string]any{ + "alg": "HS256", + "typ": "JWT", + }, + Claims: jwt.MapClaims{ + "exp": float64(time.Now().Add(900 * time.Second).Unix()), + "https://hasura.io/jwt/claims": map[string]any{ + "x-hasura-allowed-roles": []any{"user", "me"}, + "x-hasura-default-role": "user", + "x-hasura-user-id": "d0902ee3-d160-4853-af6a-8d4b6248117e", + "x-hasura-user-is-anonymous": "false", + }, + "iat": float64(time.Now().Unix()), + "iss": "hasura-auth", + "sub": "d0902ee3-d160-4853-af6a-8d4b6248117e", + }, + Signature: []byte{}, + Valid: true, + }, + jwtTokenFn: nil, + getControllerOpts: []getControllerOptsFunc{}, + }, } for _, tc := range cases { diff --git a/go/controller/post_signup_webauthn.go b/go/controller/post_signup_webauthn.go index 48cbf7c3..e8c2a6a6 100644 --- a/go/controller/post_signup_webauthn.go +++ b/go/controller/post_signup_webauthn.go @@ -50,10 +50,11 @@ func (ctrl *Controller) PostSignupWebauthn( //nolint:ireturn } user := WebauthnUser{ - ID: uuid.New(), - Name: deptr(options.DisplayName), - Email: string(request.Body.Email), - Credentials: nil, + ID: uuid.New(), + Name: deptr(options.DisplayName), + Email: string(request.Body.Email), + Credentials: nil, + Discoverable: false, } creation, apiErr := ctrl.Webauthn.BeginRegistration(user, options, logger) diff --git a/go/controller/post_signup_webauthn_test.go b/go/controller/post_signup_webauthn_test.go index ff685fcb..28c49f13 100644 --- a/go/controller/post_signup_webauthn_test.go +++ b/go/controller/post_signup_webauthn_test.go @@ -99,10 +99,11 @@ func TestPostSignupWebauthn(t *testing.T) { //nolint:maintidx RelyingPartyID: "react-apollo.example.nhost.io", }, User: controller.WebauthnUser{ - ID: uuid.UUID{}, - Name: "jane@acme.com", - Email: "jane@acme.com", - Credentials: nil, + ID: uuid.UUID{}, + Name: "jane@acme.com", + Email: "jane@acme.com", + Credentials: nil, + Discoverable: false, }, Options: &api.SignUpOptions{ AllowedRoles: &[]string{"user", "me"}, @@ -195,10 +196,11 @@ func TestPostSignupWebauthn(t *testing.T) { //nolint:maintidx RelyingPartyID: "react-apollo.example.nhost.io", }, User: controller.WebauthnUser{ - ID: uuid.UUID{}, - Name: "Jane Doe", - Email: "jane@acme.com", - Credentials: nil, + ID: uuid.UUID{}, + Name: "Jane Doe", + Email: "jane@acme.com", + Credentials: nil, + Discoverable: false, }, Options: &api.SignUpOptions{ AllowedRoles: &[]string{"user"}, @@ -342,10 +344,11 @@ func TestPostSignupWebauthn(t *testing.T) { //nolint:maintidx RelyingPartyID: "react-apollo.example.nhost.io", }, User: controller.WebauthnUser{ - ID: uuid.UUID{}, - Name: "jane@acme.com", - Email: "jane@acme.com", - Credentials: nil, + ID: uuid.UUID{}, + Name: "jane@acme.com", + Email: "jane@acme.com", + Credentials: nil, + Discoverable: false, }, Options: &api.SignUpOptions{ AllowedRoles: &[]string{"user", "me"}, diff --git a/go/controller/webauthn.go b/go/controller/webauthn.go index 34dd39ad..f8bc5c4d 100644 --- a/go/controller/webauthn.go +++ b/go/controller/webauthn.go @@ -2,7 +2,6 @@ package controller import ( "bytes" - "encoding/json" "fmt" "log/slog" "time" @@ -14,13 +13,18 @@ import ( ) type WebauthnUser struct { - ID uuid.UUID - Name string - Email string - Credentials []webauthn.Credential + ID uuid.UUID + Name string + Email string + Credentials []webauthn.Credential + UserHandle []byte + Discoverable bool } func (u WebauthnUser) WebAuthnID() []byte { + if u.Discoverable { + return u.UserHandle + } return []byte(u.ID.String()) } @@ -181,6 +185,7 @@ func (w *Webauthn) BeginLogin( func (w *Webauthn) FinishLogin( response *protocol.ParsedCredentialAssertionData, + userHandler webauthn.DiscoverableUserHandler, logger *slog.Logger, ) (*webauthn.Credential, WebauthnUser, *APIError) { challenge, ok := w.Storage[response.Response.CollectedClientData.Challenge] @@ -189,13 +194,8 @@ func (w *Webauthn) FinishLogin( return nil, WebauthnUser{}, ErrInvalidRequest } - // we do this in case the userHandle hasn't been urlencoded by the library - b, err := json.Marshal(protocol.URLEncodedBase64(response.Response.UserHandle)) - if err == nil { - potentialUUID, err := uuid.Parse(string(b)) - if err == nil && bytes.Equal(potentialUUID[:], challenge.User.ID[:]) { - response.Response.UserHandle = challenge.User.WebAuthnID() - } + if challenge.User.Discoverable { + return w.FinishDiscoverableLogin(response, userHandler, logger) } // we don't track the flags so we just copy them @@ -213,7 +213,55 @@ func (w *Webauthn) FinishLogin( cred, err := w.wa.ValidateLogin(challenge.User, challenge.Session, response) if err != nil { - logger.Info("failed to create webauthn credential", logError(err)) + logger.Info("failed to validate webauthn login", logError(err)) + return nil, WebauthnUser{}, ErrInvalidRequest + } + + w.cleanCache() + + return cred, challenge.User, nil +} + +func (w *Webauthn) BeginDiscoverableLogin( + logger *slog.Logger, +) (*protocol.CredentialAssertion, *APIError) { + w.cleanCache() + + challenge, sessionData, err := w.wa.BeginDiscoverableLogin() + if err != nil { + logger.Error("failed to begin discoverable webauthn login", logError(err)) + return nil, ErrInternalServerError + } + + w.Storage[challenge.Response.Challenge.String()] = WebauthnChallenge{ + Session: *sessionData, + User: WebauthnUser{ + ID: uuid.Nil, + Name: "", + Email: "", + Credentials: []webauthn.Credential{}, + Discoverable: true, + }, + Options: nil, + } + + return challenge, nil +} + +func (w *Webauthn) FinishDiscoverableLogin( + response *protocol.ParsedCredentialAssertionData, + userHandler webauthn.DiscoverableUserHandler, + logger *slog.Logger, +) (*webauthn.Credential, WebauthnUser, *APIError) { + challenge, ok := w.Storage[response.Response.CollectedClientData.Challenge] + if !ok { + logger.Info("webauthn challenge not found") + return nil, WebauthnUser{}, ErrInvalidRequest + } + + cred, err := w.wa.ValidateDiscoverableLogin(userHandler, challenge.Session, response) + if err != nil { + logger.Info("failed to validate webauthn discoverable login", logError(err)) return nil, WebauthnUser{}, ErrInvalidRequest } diff --git a/index.html b/index.html new file mode 100644 index 00000000..6d01a7c1 --- /dev/null +++ b/index.html @@ -0,0 +1,194 @@ + + + + + WebAuthn Signin + + +

WebAuthn Signin

+ + + + + + From 71f708e18d816c272e70d3484d6decbdaf09e76e Mon Sep 17 00:00:00 2001 From: David Barroso Date: Fri, 3 Jan 2025 08:26:00 +0100 Subject: [PATCH 2/4] asd --- go/controller/post_signin_webauthn.go | 1 + go/controller/post_signin_webauthn_verify.go | 7 +- .../post_signin_webauthn_verify_test.go | 98 ------------------- go/controller/post_signup_webauthn.go | 1 + go/controller/post_signup_webauthn_test.go | 3 + go/controller/webauthn.go | 11 +++ 6 files changed, 17 insertions(+), 104 deletions(-) diff --git a/go/controller/post_signin_webauthn.go b/go/controller/post_signin_webauthn.go index e12cf21c..4f85ead3 100644 --- a/go/controller/post_signin_webauthn.go +++ b/go/controller/post_signin_webauthn.go @@ -85,6 +85,7 @@ func (ctrl *Controller) PostSigninWebauthn( //nolint:ireturn Email: user.Email.String, Credentials: creds, Discoverable: false, + UserHandle: nil, } creation, apiErr := ctrl.Webauthn.BeginLogin(waUser, logger) diff --git a/go/controller/post_signin_webauthn_verify.go b/go/controller/post_signin_webauthn_verify.go index 47bd4dc3..f43a82da 100644 --- a/go/controller/post_signin_webauthn_verify.go +++ b/go/controller/post_signin_webauthn_verify.go @@ -51,7 +51,6 @@ func (ctrl *Controller) PostSigninWebauthnVerifyUserHandle( creds[i] = userCreds } } - fmt.Println(userID) return WebauthnUser{ ID: userID, @@ -90,11 +89,7 @@ func (ctrl *Controller) PostSigninWebauthnVerify( //nolint:ireturn return ctrl.sendError(apiErr), nil } - b, err := json.Marshal(protocol.URLEncodedBase64(credData.Response.UserHandle)) - if err != nil { - return nil, fmt.Errorf("failed to marshal user handle: %w", err) - } - userID, err := uuid.Parse(string(b)) + userID, err := uuid.Parse(string(credData.Response.UserHandle)) if err != nil { return nil, fmt.Errorf("failed to parse user ID: %w", err) } diff --git a/go/controller/post_signin_webauthn_verify_test.go b/go/controller/post_signin_webauthn_verify_test.go index 04bf3396..6538441b 100644 --- a/go/controller/post_signin_webauthn_verify_test.go +++ b/go/controller/post_signin_webauthn_verify_test.go @@ -236,104 +236,6 @@ func TestPostSigninWebauthnVerify(t *testing.T) { //nolint:maintidx jwtTokenFn: nil, getControllerOpts: []getControllerOptsFunc{}, }, - - { - name: "user discoverable", - config: func() *controller.Config { - config := getConfig() - config.WebauthnRPOrigins = []string{"http://localhost:3000"} - config.WebauthnRPID = "localhost" - config.WebauthnRPName = "React pollo Example" - - return config - }, - db: func(ctrl *gomock.Controller) controller.DBClient { - mock := mock.NewMockDBClient(ctrl) - - mock.EXPECT().GetUser( - gomock.Any(), userID, - ).Return(getSigninUser(userID), nil) - - mock.EXPECT().GetUserRoles( - gomock.Any(), userID, - ).Return([]sql.AuthUserRole{ - {UserID: userID, Role: "user"}, //nolint:exhaustruct - {UserID: userID, Role: "me"}, //nolint:exhaustruct - }, nil) - - mock.EXPECT().InsertRefreshtoken( - gomock.Any(), - cmpDBParams(sql.InsertRefreshtokenParams{ - UserID: userID, - RefreshTokenHash: pgtype.Text{}, //nolint:exhaustruct - ExpiresAt: sql.TimestampTz(time.Now().Add(30 * 24 * time.Hour)), - Type: sql.RefreshTokenTypeRegular, - Metadata: nil, - }), - ).Return(refreshTokenID, nil) - - mock.EXPECT().UpdateUserLastSeen( - gomock.Any(), userID, - ).Return(sql.TimestampTz(time.Now()), nil) - - return mock - }, - request: api.PostSigninWebauthnVerifyRequestObject{ - Body: unmarshalRequest( - t, - []byte( - `{"email":"whasd@asd.com","credential":{"id":"rkT-z-JhiBWGseoxXEKPulXcKcM","rawId":"rkT-z-JhiBWGseoxXEKPulXcKcM","response":{"authenticatorData":"SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MdAAAAAA","clientDataJSON":"eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoibk02b204bHp2VDVveHZSQ0Z1QXFSRE9qLXRsQXE4RmRQLWVSTk93c2ZncyIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCJ9","signature":"MEYCIQDAjwCZjJdHQub-tZHyXKLYdm4_IYefv2p-V8Z5k8a9lwIhAOhV5Kc5po30xgAc3XrzSiwy-Q5ItdcIMXPP5-4FvHOt","userHandle":"d0902ee3-d160-4853-af6a-8d4b6248117e"},"type":"public-key","clientExtensionResults":{},"authenticatorAttachment":"platform"}}`, //nolint:lll - ), - ), - }, - expectedResponse: api.PostSigninWebauthnVerify200JSONResponse{ - Session: &api.Session{ - AccessToken: "", - AccessTokenExpiresIn: 900, - RefreshTokenId: "c3b747ef-76a9-4c56-8091-ed3e6b8afb2c", - RefreshToken: "1fb17604-86c7-444e-b337-09a644465f2d", - User: &api.User{ - AvatarUrl: "", - CreatedAt: time.Now(), - DefaultRole: "user", - DisplayName: "Jane Doe", - Email: ptr(types.Email("jane@acme.com")), - EmailVerified: true, - Id: "d0902ee3-d160-4853-af6a-8d4b6248117e", - IsAnonymous: false, - Locale: "en", - Metadata: map[string]any{}, - PhoneNumber: nil, - PhoneNumberVerified: false, - Roles: []string{"user", "me"}, - }, - }, - }, - expectedJWT: &jwt.Token{ - Raw: "", - Method: jwt.SigningMethodHS256, - Header: map[string]any{ - "alg": "HS256", - "typ": "JWT", - }, - Claims: jwt.MapClaims{ - "exp": float64(time.Now().Add(900 * time.Second).Unix()), - "https://hasura.io/jwt/claims": map[string]any{ - "x-hasura-allowed-roles": []any{"user", "me"}, - "x-hasura-default-role": "user", - "x-hasura-user-id": "d0902ee3-d160-4853-af6a-8d4b6248117e", - "x-hasura-user-is-anonymous": "false", - }, - "iat": float64(time.Now().Unix()), - "iss": "hasura-auth", - "sub": "d0902ee3-d160-4853-af6a-8d4b6248117e", - }, - Signature: []byte{}, - Valid: true, - }, - jwtTokenFn: nil, - getControllerOpts: []getControllerOptsFunc{}, - }, } for _, tc := range cases { diff --git a/go/controller/post_signup_webauthn.go b/go/controller/post_signup_webauthn.go index e8c2a6a6..00c5deb7 100644 --- a/go/controller/post_signup_webauthn.go +++ b/go/controller/post_signup_webauthn.go @@ -55,6 +55,7 @@ func (ctrl *Controller) PostSignupWebauthn( //nolint:ireturn Email: string(request.Body.Email), Credentials: nil, Discoverable: false, + UserHandle: nil, } creation, apiErr := ctrl.Webauthn.BeginRegistration(user, options, logger) diff --git a/go/controller/post_signup_webauthn_test.go b/go/controller/post_signup_webauthn_test.go index 28c49f13..cadef151 100644 --- a/go/controller/post_signup_webauthn_test.go +++ b/go/controller/post_signup_webauthn_test.go @@ -104,6 +104,7 @@ func TestPostSignupWebauthn(t *testing.T) { //nolint:maintidx Email: "jane@acme.com", Credentials: nil, Discoverable: false, + UserHandle: nil, }, Options: &api.SignUpOptions{ AllowedRoles: &[]string{"user", "me"}, @@ -201,6 +202,7 @@ func TestPostSignupWebauthn(t *testing.T) { //nolint:maintidx Email: "jane@acme.com", Credentials: nil, Discoverable: false, + UserHandle: nil, }, Options: &api.SignUpOptions{ AllowedRoles: &[]string{"user"}, @@ -349,6 +351,7 @@ func TestPostSignupWebauthn(t *testing.T) { //nolint:maintidx Email: "jane@acme.com", Credentials: nil, Discoverable: false, + UserHandle: nil, }, Options: &api.SignUpOptions{ AllowedRoles: &[]string{"user", "me"}, diff --git a/go/controller/webauthn.go b/go/controller/webauthn.go index f8bc5c4d..9889306f 100644 --- a/go/controller/webauthn.go +++ b/go/controller/webauthn.go @@ -2,6 +2,7 @@ package controller import ( "bytes" + "encoding/json" "fmt" "log/slog" "time" @@ -211,6 +212,15 @@ func (w *Webauthn) FinishLogin( } } + // we do this in case the userHandle hasn't been urlencoded by the library + b, err := json.Marshal(protocol.URLEncodedBase64(response.Response.UserHandle)) + if err == nil { + potentialUUID, err := uuid.Parse(string(b)) + if err == nil && bytes.Equal(potentialUUID[:], challenge.User.ID[:]) { + response.Response.UserHandle = challenge.User.WebAuthnID() + } + } + cred, err := w.wa.ValidateLogin(challenge.User, challenge.Session, response) if err != nil { logger.Info("failed to validate webauthn login", logError(err)) @@ -241,6 +251,7 @@ func (w *Webauthn) BeginDiscoverableLogin( Email: "", Credentials: []webauthn.Credential{}, Discoverable: true, + UserHandle: nil, }, Options: nil, } From 5551ce914732d4a08805a450f3bc116bf3eb30b2 Mon Sep 17 00:00:00 2001 From: David Barroso Date: Wed, 8 Jan 2025 11:20:32 +0100 Subject: [PATCH 3/4] asd --- go/controller/post_signin_webauthn.go | 1 - go/controller/post_signin_webauthn_verify.go | 4 +++- go/controller/post_signup_webauthn.go | 1 - go/controller/post_signup_webauthn_test.go | 3 --- go/controller/webauthn.go | 5 ----- 5 files changed, 3 insertions(+), 11 deletions(-) diff --git a/go/controller/post_signin_webauthn.go b/go/controller/post_signin_webauthn.go index 4f85ead3..e12cf21c 100644 --- a/go/controller/post_signin_webauthn.go +++ b/go/controller/post_signin_webauthn.go @@ -85,7 +85,6 @@ func (ctrl *Controller) PostSigninWebauthn( //nolint:ireturn Email: user.Email.String, Credentials: creds, Discoverable: false, - UserHandle: nil, } creation, apiErr := ctrl.Webauthn.BeginLogin(waUser, logger) diff --git a/go/controller/post_signin_webauthn_verify.go b/go/controller/post_signin_webauthn_verify.go index f43a82da..ac5ed3ce 100644 --- a/go/controller/post_signin_webauthn_verify.go +++ b/go/controller/post_signin_webauthn_verify.go @@ -40,6 +40,7 @@ func (ctrl *Controller) PostSigninWebauthnVerifyUserHandle( return nil, apiErr } + // we don't track the flags so we just copy them for i, userCreds := range creds { if bytes.Equal(response.RawID, userCreds.ID) { userCreds.Flags = webauthn.CredentialFlags{ @@ -52,13 +53,14 @@ func (ctrl *Controller) PostSigninWebauthnVerifyUserHandle( } } + response.Response.UserHandle = []byte(userID.String()) + return WebauthnUser{ ID: userID, Name: "", Email: "", Credentials: creds, Discoverable: true, - UserHandle: userHandle, }, nil } } diff --git a/go/controller/post_signup_webauthn.go b/go/controller/post_signup_webauthn.go index 00c5deb7..e8c2a6a6 100644 --- a/go/controller/post_signup_webauthn.go +++ b/go/controller/post_signup_webauthn.go @@ -55,7 +55,6 @@ func (ctrl *Controller) PostSignupWebauthn( //nolint:ireturn Email: string(request.Body.Email), Credentials: nil, Discoverable: false, - UserHandle: nil, } creation, apiErr := ctrl.Webauthn.BeginRegistration(user, options, logger) diff --git a/go/controller/post_signup_webauthn_test.go b/go/controller/post_signup_webauthn_test.go index cadef151..28c49f13 100644 --- a/go/controller/post_signup_webauthn_test.go +++ b/go/controller/post_signup_webauthn_test.go @@ -104,7 +104,6 @@ func TestPostSignupWebauthn(t *testing.T) { //nolint:maintidx Email: "jane@acme.com", Credentials: nil, Discoverable: false, - UserHandle: nil, }, Options: &api.SignUpOptions{ AllowedRoles: &[]string{"user", "me"}, @@ -202,7 +201,6 @@ func TestPostSignupWebauthn(t *testing.T) { //nolint:maintidx Email: "jane@acme.com", Credentials: nil, Discoverable: false, - UserHandle: nil, }, Options: &api.SignUpOptions{ AllowedRoles: &[]string{"user"}, @@ -351,7 +349,6 @@ func TestPostSignupWebauthn(t *testing.T) { //nolint:maintidx Email: "jane@acme.com", Credentials: nil, Discoverable: false, - UserHandle: nil, }, Options: &api.SignUpOptions{ AllowedRoles: &[]string{"user", "me"}, diff --git a/go/controller/webauthn.go b/go/controller/webauthn.go index 9889306f..daa45930 100644 --- a/go/controller/webauthn.go +++ b/go/controller/webauthn.go @@ -18,14 +18,10 @@ type WebauthnUser struct { Name string Email string Credentials []webauthn.Credential - UserHandle []byte Discoverable bool } func (u WebauthnUser) WebAuthnID() []byte { - if u.Discoverable { - return u.UserHandle - } return []byte(u.ID.String()) } @@ -251,7 +247,6 @@ func (w *Webauthn) BeginDiscoverableLogin( Email: "", Credentials: []webauthn.Credential{}, Discoverable: true, - UserHandle: nil, }, Options: nil, } From 5facbf45dc47c008a7495498e5e3b17febc73b59 Mon Sep 17 00:00:00 2001 From: David Barroso Date: Wed, 8 Jan 2025 12:35:06 +0100 Subject: [PATCH 4/4] asd --- .../post_signin_webauthn_verify_test.go | 147 +++++++++++++++++- 1 file changed, 145 insertions(+), 2 deletions(-) diff --git a/go/controller/post_signin_webauthn_verify_test.go b/go/controller/post_signin_webauthn_verify_test.go index 6538441b..80288f53 100644 --- a/go/controller/post_signin_webauthn_verify_test.go +++ b/go/controller/post_signin_webauthn_verify_test.go @@ -47,8 +47,8 @@ func TestPostSigninWebauthnVerify(t *testing.T) { //nolint:maintidx config: func() *controller.Config { config := getConfig() config.WebauthnRPOrigins = []string{"http://localhost:3000"} - config.WebauthnRPID = "localhost" - config.WebauthnRPName = "React pollo Example" + config.WebauthnRPID = "localhost" //nolint:goconst + config.WebauthnRPName = "React pollo Example" //nolint:goconst return config }, @@ -236,6 +236,126 @@ func TestPostSigninWebauthnVerify(t *testing.T) { //nolint:maintidx jwtTokenFn: nil, getControllerOpts: []getControllerOptsFunc{}, }, + + { + name: "success - discoverable", + config: func() *controller.Config { + config := getConfig() + config.WebauthnRPOrigins = []string{"http://localhost:3000"} + config.WebauthnRPID = "localhost" + config.WebauthnRPName = "React pollo Example" + + return config + }, + db: func(ctrl *gomock.Controller) controller.DBClient { + mock := mock.NewMockDBClient(ctrl) + + userID := uuid.MustParse("176ce216-38af-4223-af49-6be702f4676c") + + mock.EXPECT().GetSecurityKeys( + gomock.Any(), userID, + ).Return([]sql.AuthUserSecurityKey{ + { + ID: uuid.MustParse("85a4af56-6c7d-4371-ae66-d4682a660900"), + UserID: userID, + CredentialID: "4OXfDI7QSSQQsmOV4-sz6LlS8_8", + CredentialPublicKey: []byte{ + 165, 1, 2, 3, 38, 32, 1, 33, 88, 32, 7, 40, 121, 244, 90, 63, 43, 44, 129, + 197, 142, 82, 36, 179, 48, 89, 160, 215, 253, 76, 155, 37, 77, 251, 237, + 219, 111, 246, 205, 183, 77, 240, 34, 88, 32, 78, 37, 134, 117, 44, 128, + 33, 35, 73, 244, 164, 148, 110, 102, 244, 44, 7, 141, 69, 207, 34, 211, + 72, 24, 53, 58, 130, 205, 150, 71, 200, 204, + }, + Counter: 0, + Transports: "", + Nickname: pgtype.Text{}, //nolint:exhaustruct + }, + }, nil) + + mock.EXPECT().GetUser( + gomock.Any(), userID, + ).Return(getSigninUser(userID), nil) + + mock.EXPECT().GetUserRoles( + gomock.Any(), userID, + ).Return([]sql.AuthUserRole{ + {UserID: userID, Role: "user"}, //nolint:exhaustruct + {UserID: userID, Role: "me"}, //nolint:exhaustruct + }, nil) + + mock.EXPECT().InsertRefreshtoken( + gomock.Any(), + cmpDBParams(sql.InsertRefreshtokenParams{ + UserID: userID, + RefreshTokenHash: pgtype.Text{}, //nolint:exhaustruct + ExpiresAt: sql.TimestampTz(time.Now().Add(30 * 24 * time.Hour)), + Type: sql.RefreshTokenTypeRegular, + Metadata: nil, + }), + ).Return(refreshTokenID, nil) + + mock.EXPECT().UpdateUserLastSeen( + gomock.Any(), userID, + ).Return(sql.TimestampTz(time.Now()), nil) + + return mock + }, + request: api.PostSigninWebauthnVerifyRequestObject{ + Body: unmarshalRequest( + t, + []byte( + `{"credential":{"id":"4OXfDI7QSSQQsmOV4-sz6LlS8_8","rawId":"4OXfDI7QSSQQsmOV4-sz6LlS8_8","response":{"authenticatorData":"SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MdAAAAAA","clientDataJSON":"eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiMndUMjlCM0RhUmlIbmEzYWoxNEpsVEMtT1hqZ0lja3dCQzM1bXl6X1RfbyIsIm9yaWdpbiI6Imh0dHA6Ly9sb2NhbGhvc3Q6MzAwMCIsImNyb3NzT3JpZ2luIjpmYWxzZX0","signature":"MEUCIFTNIExdczBeaM8MrMlBYVe1mAAzBBoTAaMzK2Mzo7geAiEAuIQH3CfMo1hRXWayZ-TXxu3m6evTBZBhJWvsI_d7ypI","userHandle":"176ce216-38af-4223-af49-6be702f4676c"},"type":"public-key"}}`, //nolint:lll + ), + ), + }, + expectedResponse: api.PostSigninWebauthnVerify200JSONResponse{ + Session: &api.Session{ + AccessToken: "", + AccessTokenExpiresIn: 900, + RefreshTokenId: "c3b747ef-76a9-4c56-8091-ed3e6b8afb2c", + RefreshToken: "1fb17604-86c7-444e-b337-09a644465f2d", + User: &api.User{ + AvatarUrl: "", + CreatedAt: time.Now(), + DefaultRole: "user", + DisplayName: "Jane Doe", + Email: ptr(types.Email("jane@acme.com")), + EmailVerified: true, + Id: "176ce216-38af-4223-af49-6be702f4676c", + IsAnonymous: false, + Locale: "en", + Metadata: map[string]any{}, + PhoneNumber: nil, + PhoneNumberVerified: false, + Roles: []string{"user", "me"}, + }, + }, + }, + expectedJWT: &jwt.Token{ + Raw: "", + Method: jwt.SigningMethodHS256, + Header: map[string]any{ + "alg": "HS256", + "typ": "JWT", + }, + Claims: jwt.MapClaims{ + "exp": float64(time.Now().Add(900 * time.Second).Unix()), + "https://hasura.io/jwt/claims": map[string]any{ + "x-hasura-allowed-roles": []any{"user", "me"}, + "x-hasura-default-role": "user", + "x-hasura-user-id": "176ce216-38af-4223-af49-6be702f4676c", + "x-hasura-user-is-anonymous": "false", + }, + "iat": float64(time.Now().Unix()), + "iss": "hasura-auth", + "sub": "176ce216-38af-4223-af49-6be702f4676c", + }, + Signature: []byte{}, + Valid: true, + }, + jwtTokenFn: nil, + getControllerOpts: []getControllerOptsFunc{}, + }, } for _, tc := range cases { @@ -297,8 +417,31 @@ func TestPostSigninWebauthnVerify(t *testing.T) { //nolint:maintidx t.Fatal(err) } + b = []byte(`{ + "Session": { + "challenge": "2wT29B3DaRiHna3aj14JlTC-OXjgIckwBC35myz_T_o", + "rpId": "localhost", + "user_id": null, + "expires": "2025-01-08T12:25:01.688438+01:00", + "userVerification": "preferred" + }, + "User": { + "ID": "00000000-0000-0000-0000-000000000000", + "Name": "", + "Email": "", + "Credentials": [], + "Discoverable": true + }, + "Options": null + }`) + var sessionDataDiscoverable controller.WebauthnChallenge + if err := json.Unmarshal(b, &sessionDataDiscoverable); err != nil { + t.Fatal(err) + } + if c.Webauthn != nil { c.Webauthn.Storage["nM6om8lzvT5oxvRCFuAqRDOj-tlAq8FdP-eRNOwsfgs"] = sessionData + c.Webauthn.Storage["2wT29B3DaRiHna3aj14JlTC-OXjgIckwBC35myz_T_o"] = sessionDataDiscoverable } resp := assertRequest(