From 9d16ab7dba6650bd40e82dc134a00fbc2b685a8d Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 8 Apr 2020 18:44:30 +0200 Subject: [PATCH] Sign in with Apple (#11793) * add date check * achievements modal polishing * refresh private-messages page when you are already on it * add countbadge knob to change the example * fix lint * typos * typos * typos * add toggle for achievements categories * typo * fix test * fix edit avatar modal cannot be closed * WIP(settings): subscriber page improvements * WIP(subscriptions): more design build-out * fix(css): disabled button styles * fix(css): better Amazon targeting * fix hide tooltip + align header correctly * disable perfect scroll * load messages on refresh event * fix header label + conversation actions not breaking layout on hover * WIP(g1g1): notif * WIP(g1g1): notif cont'd * fix(test): snowball change * fix(event): feature NYE card * chore(sprites): compile * fix(bgs): include TT required field * add gifting banner to the max height calculation * chore(event): enable winter customizations * WIP(gifting): partial modal implementation * feat(gifting): select giftee modal * fix(gifting): notification order, modal dismiss * Begin implementing sign in with apple # Conflicts: # package-lock.json # website/common/script/constants.js # website/server/libs/auth/social.js # website/server/models/user/schema.js * Add apple sign in button to website * fix lint errors * fix config json * fix(modals): correct some repops * fix(gifting): style updates * fix(buy): modal style changes * fix(modals): also clean out "prev" * Attempt workaround for sign in with apple on android * temporarily log everything as error * refactor(modals): hide in dismiss event * fix temporary test failure * changes to sign in with apple * fix: first batch of layout issues for private messages + auto sizing textarea * fix(modals): new dismiss logic * fix(modals): new dismiss no go?? * Only use email scope * print debugging * . * .. * ... * username second line - open profile on face-avatar/conversation name - fix textarea height * temporarily disable apple auth and just return data for debugging * Hopefully this works * ..... * WIP(subscription): unsubscribed state * . * .. * MAYBE THIS ACTUALLY WORKS??? * Implement apple sign in * fix some urls * fix urls * fix redirect and auth * attempt to also request name * fix lint error * WIP(subscription): partial subscribed * chore(sprites): compile * Change approach so that it actually works * fix config error * fix lint errors * Fix * fix lint error * lint error * WIP(subscription): finish subscribed * refresh on sync * new "you dont have any messages" style + changed min textarea height * new conversationItem style / layout * reset message unread on reload * chore(npm): update package-locks * fix styles / textarea height * feat(subscription): revised sub page RC * list optOut / chatRevoked informations for each conversation + show why its disabled * Improve apple redirect view * Fix apple icon on group task registration page * WIP(adventure): prereqs * Block / Unblock - correct disabled states - $gray-200 instead of 300/400 * canReceive not checking chatRevoked * fix: faceAvatar / userLink open the selected conversation user * check if the target user is blocking the logged-in user * fix(subs): style tweaks * fix(profiles): short circuit contributor Attempted fix for #11830 * chore(sprites): compile * fix(content): missing potion data * fix(content): missing string * WIP(drops): new modal * fix(subs): moar style tweaks * check if blocks is undefined * max-height instead of height * fix "no messages" state + canReceive on a new conversation * WIP(adventure): analytics fixes etc * Improve apple signin handling * fixed conversations width (280px on max 768 width page) * feat(adventure): random egg+potion on 2nd task * fix(lint): noworkies * fix(modal): correctly construct classes * fix(tests): expectations and escape * Fix typo * use base url from env variables * fix lint * call autosize after message is sent * fix urls * always verify token * throw error when social auth could not retrieve id * Store emails correctly for apple auth * Retrieve name when authenticating through apple * Fix lint errors * fix all lint errors * fix(content): missing strings * Revert "always verify token" This reverts commit 8ac40c76bfa880f68fa3ce350a86ce2151b9cf95. # Conflicts: # website/server/libs/auth/social.js * Correctly load name * remove extra changes * remove extra logger call * reset package and package-lock * add back missing packages * use name from apple * add support for multiple apple public keys * add some unit and integration tests * add apple auth integration test * tweak social signup buttons * pixel pushing Co-authored-by: Matteo Pagliazzi Co-authored-by: Sabe Jones Co-authored-by: negue Co-authored-by: Phillip Thelen --- .gitignore | 1 + .nodemonignore | 1 + config.json.example | 4 + package-lock.json | 436 +++++++++++++++--- package.json | 3 + test/api/unit/libs/email.test.js | 48 ++ .../v3/integration/user/DELETE-user.test.js | 19 + .../DELETE-user_auth_social_network.test.js | 38 ++ .../user/auth/GET-user_auth_apple.test.js | 50 ++ .../user/auth/POST-register_local.test.js | 68 +++ .../user/auth/POST-user_auth_social.test.js | 2 + .../v4/user/auth/POST-register_local.test.js | 68 +++ website/client/src/assets/svg/apple.svg | 4 + website/client/src/assets/svg/apple_black.svg | 55 +++ .../client/src/components/auth/authForm.vue | 72 ++- .../components/auth/registerLoginReset.vue | 129 ++++-- .../client/src/components/settings/site.vue | 16 +- .../src/components/static/appleRedirect.vue | 35 ++ website/client/src/components/static/home.vue | 109 +++-- website/client/src/libs/auth.js | 5 + website/client/src/router/index.js | 4 + website/client/src/store/actions/auth.js | 21 + website/client/vue.config.js | 1 + website/common/script/constants.js | 2 + website/server/controllers/api-v3/auth.js | 35 ++ website/server/libs/auth/apple.js | 52 +++ website/server/libs/auth/social.js | 16 +- website/server/libs/auth/utils.js | 7 +- website/server/models/user/schema.js | 1 + 29 files changed, 1129 insertions(+), 173 deletions(-) create mode 100644 test/api/v3/integration/user/auth/GET-user_auth_apple.test.js create mode 100644 website/client/src/assets/svg/apple.svg create mode 100644 website/client/src/assets/svg/apple_black.svg create mode 100644 website/client/src/components/static/appleRedirect.vue create mode 100644 website/server/libs/auth/apple.js diff --git a/.gitignore b/.gitignore index 365bcc03a3..95db24e4d7 100644 --- a/.gitignore +++ b/.gitignore @@ -37,6 +37,7 @@ yarn.lock .elasticbeanstalk/* !.elasticbeanstalk/*.cfg.yml !.elasticbeanstalk/*.global.yml +/.vscode # webstorm fake webpack for path intellisense webpack.webstorm.config diff --git a/.nodemonignore b/.nodemonignore index dc58adb455..e9ce1bfaa3 100644 --- a/.nodemonignore +++ b/.nodemonignore @@ -9,3 +9,4 @@ test/** *.swp *.swx website/raw_sprites/** +content_cache/** diff --git a/config.json.example b/config.json.example index 3f6ba7769e..03a3994cb0 100644 --- a/config.json.example +++ b/config.json.example @@ -75,5 +75,9 @@ "WEB_CONCURRENCY": 1, "SKIP_SSL_CHECK_KEY": "key", "ENABLE_STACKDRIVER_TRACING": "false", + "APPLE_AUTH_PRIVATE_KEY": "", + "APPLE_TEAM_ID": "", + "APPLE_AUTH_CLIENT_ID": "", + "APPLE_AUTH_KEY_ID": "", "BLOCKED_IPS": "" } diff --git a/package-lock.json b/package-lock.json index d60a0290a4..9ff53ba7eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1472,6 +1472,15 @@ "defer-to-connect": "^2.0.0" } }, + "@types/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, "@types/cacheable-request": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz", @@ -1489,11 +1498,56 @@ "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", "dev": true }, + "@types/connect": { + "version": "3.4.33", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz", + "integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==", + "requires": { + "@types/node": "*" + } + }, "@types/events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==" }, + "@types/express": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.4.tgz", + "integrity": "sha512-DO1L53rGqIDUEvOjJKmbMEQ5Z+BM2cIEPy/eV3En+s166Gz+FeuzRerxcab757u/U4v4XF4RYrZPmqKa+aY/2w==", + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "*", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-jwt": { + "version": "0.0.42", + "resolved": "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-0.0.42.tgz", + "integrity": "sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag==", + "requires": { + "@types/express": "*", + "@types/express-unless": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.3", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.3.tgz", + "integrity": "sha512-sHEsvEzjqN+zLbqP+8OXTipc10yH1QLR+hnr5uw29gi9AhCAAAdri8ClNV7iMdrJrIzXIQtlkPvq8tJGhj3QJQ==", + "requires": { + "@types/node": "*", + "@types/range-parser": "*" + } + }, + "@types/express-unless": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.5.1.tgz", + "integrity": "sha512-5fuvg7C69lemNgl0+v+CUxDYWVPSfXHhJPst4yTLcqi4zKJpORCxnDrnnilk3k0DTq/WrAUdvXFs01+vUqUZHw==", + "requires": { + "@types/express": "*" + } + }, "@types/form-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.5.0.tgz", @@ -1533,6 +1587,11 @@ "@types/node": "*" } }, + "@types/mime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz", + "integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==" + }, "@types/minimatch": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", @@ -1570,6 +1629,16 @@ "integrity": "sha512-ce5d3q03Ex0sy4R14722Rmt6MT07Ua+k4FwDfdcToYJcMKNtRVQvJ6JCAPdAmAnbRb6CsX6aYb9m96NGod9uTw==", "optional": true }, + "@types/qs": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.1.tgz", + "integrity": "sha512-lhbQXx9HKZAPgBkISrBcmAcMpZsmpe/Cd/hY7LGZS5OfkySUBItnPZHgQPssWYUET8elF+yCFBbP1Q0RZPTdaw==" + }, + "@types/range-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" + }, "@types/responselike": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", @@ -1583,6 +1652,15 @@ "resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz", "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==" }, + "@types/serve-static": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz", + "integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==", + "requires": { + "@types/express-serve-static-core": "*", + "@types/mime": "*" + } + }, "@types/ws": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-5.1.2.tgz", @@ -2029,6 +2107,16 @@ "default-require-extensions": "^1.0.0" } }, + "apple-auth": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/apple-auth/-/apple-auth-1.0.5.tgz", + "integrity": "sha512-EbMm0rqWyUANxHJfiNGrvLkS3acdBfAj0TJ1hnA/jxq7+l9GTd2br5ajgjBjcHT5bbN6CsvuZFAdYofzn1J/6A==", + "requires": { + "axios": "^0.19.0", + "express": "^4.17.1", + "jsonwebtoken": "^8.5.1" + } + }, "aproba": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", @@ -2353,7 +2441,6 @@ "version": "0.19.2", "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", - "dev": true, "requires": { "follow-redirects": "1.5.10" } @@ -6056,22 +6143,26 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "optional": true }, "ansi-regex": { "version": "2.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "optional": true }, "aproba": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "optional": true }, "are-we-there-yet": { "version": "1.1.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "optional": true, "requires": { "delegates": "^1.0.0", @@ -6080,12 +6171,14 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "optional": true }, "brace-expansion": { "version": "1.1.11", - "bundled": true, + "resolved": false, + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "optional": true, "requires": { "balanced-match": "^1.0.0", @@ -6094,32 +6187,38 @@ }, "chownr": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", "optional": true }, "code-point-at": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "optional": true }, "concat-map": { "version": "0.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "optional": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "optional": true }, "core-util-is": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "optional": true }, "debug": { "version": "4.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "optional": true, "requires": { "ms": "^2.1.1" @@ -6127,22 +6226,26 @@ }, "deep-extend": { "version": "0.6.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "optional": true }, "delegates": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "optional": true }, "detect-libc": { "version": "1.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "optional": true }, "fs-minipass": { "version": "1.2.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "optional": true, "requires": { "minipass": "^2.2.1" @@ -6150,12 +6253,14 @@ }, "fs.realpath": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "optional": true }, "gauge": { "version": "2.7.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "optional": true, "requires": { "aproba": "^1.0.3", @@ -6170,7 +6275,8 @@ }, "glob": { "version": "7.1.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "optional": true, "requires": { "fs.realpath": "^1.0.0", @@ -6183,12 +6289,14 @@ }, "has-unicode": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "optional": true }, "iconv-lite": { "version": "0.4.24", - "bundled": true, + "resolved": false, + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "optional": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" @@ -6196,7 +6304,8 @@ }, "ignore-walk": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "optional": true, "requires": { "minimatch": "^3.0.4" @@ -6204,7 +6313,8 @@ }, "inflight": { "version": "1.0.6", - "bundled": true, + "resolved": false, + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "optional": true, "requires": { "once": "^1.3.0", @@ -6213,17 +6323,20 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "optional": true }, "ini": { "version": "1.3.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "optional": true, "requires": { "number-is-nan": "^1.0.0" @@ -6231,12 +6344,14 @@ }, "isarray": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "optional": true }, "minimatch": { "version": "3.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "optional": true, "requires": { "brace-expansion": "^1.1.7" @@ -6244,12 +6359,14 @@ }, "minimist": { "version": "0.0.8", - "bundled": true, + "resolved": false, + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "optional": true }, "minipass": { "version": "2.3.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "optional": true, "requires": { "safe-buffer": "^5.1.2", @@ -6258,7 +6375,8 @@ }, "minizlib": { "version": "1.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", "optional": true, "requires": { "minipass": "^2.2.1" @@ -6266,7 +6384,8 @@ }, "mkdirp": { "version": "0.5.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "optional": true, "requires": { "minimist": "0.0.8" @@ -6274,12 +6393,14 @@ }, "ms": { "version": "2.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "optional": true }, "needle": { "version": "2.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==", "optional": true, "requires": { "debug": "^4.1.0", @@ -6289,7 +6410,8 @@ }, "node-pre-gyp": { "version": "0.12.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", "optional": true, "requires": { "detect-libc": "^1.0.2", @@ -6306,7 +6428,8 @@ }, "nopt": { "version": "4.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "optional": true, "requires": { "abbrev": "1", @@ -6315,12 +6438,14 @@ }, "npm-bundled": { "version": "1.0.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", "optional": true }, "npm-packlist": { "version": "1.4.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", "optional": true, "requires": { "ignore-walk": "^3.0.1", @@ -6329,7 +6454,8 @@ }, "npmlog": { "version": "4.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "optional": true, "requires": { "are-we-there-yet": "~1.1.2", @@ -6340,17 +6466,20 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "optional": true }, "object-assign": { "version": "4.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "optional": true }, "once": { "version": "1.4.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "optional": true, "requires": { "wrappy": "1" @@ -6358,17 +6487,20 @@ }, "os-homedir": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "optional": true }, "os-tmpdir": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "optional": true }, "osenv": { "version": "0.1.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "optional": true, "requires": { "os-homedir": "^1.0.0", @@ -6377,17 +6509,20 @@ }, "path-is-absolute": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "optional": true }, "process-nextick-args": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "optional": true }, "rc": { "version": "1.2.8", - "bundled": true, + "resolved": false, + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "optional": true, "requires": { "deep-extend": "^0.6.0", @@ -6398,14 +6533,16 @@ "dependencies": { "minimist": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "optional": true } } }, "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "optional": true, "requires": { "core-util-is": "~1.0.0", @@ -6419,7 +6556,8 @@ }, "rimraf": { "version": "2.6.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "optional": true, "requires": { "glob": "^7.1.3" @@ -6427,37 +6565,44 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "optional": true }, "safer-buffer": { "version": "2.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "optional": true }, "sax": { "version": "1.2.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "optional": true }, "semver": { "version": "5.7.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "optional": true }, "set-blocking": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "optional": true }, "signal-exit": { "version": "3.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "optional": true }, "string-width": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "optional": true, "requires": { "code-point-at": "^1.0.0", @@ -6467,7 +6612,8 @@ }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "optional": true, "requires": { "safe-buffer": "~5.1.0" @@ -6475,7 +6621,8 @@ }, "strip-ansi": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "optional": true, "requires": { "ansi-regex": "^2.0.0" @@ -6483,12 +6630,14 @@ }, "strip-json-comments": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "optional": true }, "tar": { "version": "4.4.8", - "bundled": true, + "resolved": false, + "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", "optional": true, "requires": { "chownr": "^1.1.1", @@ -6502,12 +6651,14 @@ }, "util-deprecate": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "optional": true }, "wide-align": { "version": "1.1.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "optional": true, "requires": { "string-width": "^1.0.2 || 2" @@ -6515,12 +6666,14 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "optional": true }, "yallist": { "version": "3.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "optional": true } } @@ -8658,6 +8811,125 @@ "safe-buffer": "^5.0.1" } }, + "jwks-rsa": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-1.7.0.tgz", + "integrity": "sha512-tq7DVJt9J6wTvl9+AQfwZIiPSuY2Vf0F+MovfRTFuBqLB1xgDVhegD33ChEAQ6yBv9zFvUIyj4aiwrSA5VehUw==", + "requires": { + "@types/express-jwt": "0.0.42", + "debug": "^4.1.0", + "jsonwebtoken": "^8.5.1", + "limiter": "^1.1.4", + "lru-memoizer": "^2.0.1", + "ms": "^2.1.2", + "request": "^2.88.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "requires": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + } + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + } + } + }, "jws": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", @@ -8807,6 +9079,11 @@ "resolve": "^1.1.7" } }, + "limiter": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", + "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" + }, "linkify-it": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.2.0.tgz", @@ -8988,6 +9265,31 @@ "yallist": "^3.0.2" } }, + "lru-memoizer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.1.2.tgz", + "integrity": "sha512-N5L5xlnVcbIinNn/TJ17vHBZwBMt9t7aJDz2n97moWubjNl6VO9Ao2XuAGBBddkYdjrwR9HfzXbT6NfMZXAZ/A==", + "requires": { + "lodash.clonedeep": "^4.5.0", + "lru-cache": "~4.0.0" + }, + "dependencies": { + "lru-cache": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz", + "integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=", + "requires": { + "pseudomap": "^1.0.1", + "yallist": "^2.0.0" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + } + } + }, "make-dir": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", diff --git a/package.json b/package.json index 0e427aba9d..7c6ac3144f 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "amplitude": "^3.5.0", "apidoc": "^0.17.5", "apn": "^2.2.0", + "apple-auth": "^1.0.5", "aws-sdk": "^2.653.0", "bcrypt": "^3.0.8", "body-parser": "^1.18.3", @@ -41,6 +42,8 @@ "image-size": "^0.8.3", "in-app-purchase": "^1.11.3", "js2xmlparser": "^4.0.1", + "jsonwebtoken": "^8.5.1", + "jwks-rsa": "^1.7.0", "lodash": "^4.17.15", "merge-stream": "^2.0.0", "method-override": "^3.0.0", diff --git a/test/api/unit/libs/email.test.js b/test/api/unit/libs/email.test.js index e518adc0d3..7d0df88350 100644 --- a/test/api/unit/libs/email.test.js +++ b/test/api/unit/libs/email.test.js @@ -18,6 +18,16 @@ function getUser () { value: 'email@facebook', }], }, + google: { + emails: [{ + value: 'email@google', + }], + }, + apple: { + emails: [{ + value: 'email@apple', + }], + }, }, profile: { name: 'profile name', @@ -58,6 +68,8 @@ describe('emails', () => { const user = getUser(); delete user.profile.name; delete user.auth.local.email; + delete user.auth.google.emails; + delete user.auth.apple.emails; const data = getUserInfo(user, ['name', 'email', '_id', 'canSend']); @@ -67,12 +79,48 @@ describe('emails', () => { expect(data).to.have.property('canSend', true); }); + it('returns correct user data [google users]', () => { + const attachEmail = requireAgain(pathToEmailLib); + const { getUserInfo } = attachEmail; + const user = getUser(); + delete user.profile.name; + delete user.auth.local.email; + delete user.auth.facebook.emails; + delete user.auth.apple.emails; + + const data = getUserInfo(user, ['name', 'email', '_id', 'canSend']); + + expect(data).to.have.property('name', user.auth.local.username); + expect(data).to.have.property('email', user.auth.google.emails[0].value); + expect(data).to.have.property('_id', user._id); + expect(data).to.have.property('canSend', true); + }); + + it('returns correct user data [apple users]', () => { + const attachEmail = requireAgain(pathToEmailLib); + const { getUserInfo } = attachEmail; + const user = getUser(); + delete user.profile.name; + delete user.auth.local.email; + delete user.auth.google.emails; + delete user.auth.facebook.emails; + + const data = getUserInfo(user, ['name', 'email', '_id', 'canSend']); + + expect(data).to.have.property('name', user.auth.local.username); + expect(data).to.have.property('email', user.auth.apple.emails[0].value); + expect(data).to.have.property('_id', user._id); + expect(data).to.have.property('canSend', true); + }); + it('has fallbacks for missing data', () => { const attachEmail = requireAgain(pathToEmailLib); const { getUserInfo } = attachEmail; const user = getUser(); delete user.auth.local.email; delete user.auth.facebook; + delete user.auth.google; + delete user.auth.apple; const data = getUserInfo(user, ['name', 'email', '_id', 'canSend']); diff --git a/test/api/v3/integration/user/DELETE-user.test.js b/test/api/v3/integration/user/DELETE-user.test.js index 644a2d57ba..18be9792ce 100644 --- a/test/api/v3/integration/user/DELETE-user.test.js +++ b/test/api/v3/integration/user/DELETE-user.test.js @@ -346,4 +346,23 @@ describe('DELETE /user', () => { await expect(checkExistence('users', user._id)).to.eventually.eql(false); }); }); + + context('user with Apple auth', async () => { + beforeEach(async () => { + user = await generateUser({ + auth: { + apple: { + id: 'apple-id', + }, + }, + }); + }); + + it('deletes a Apple user', async () => { + await user.del('/user', { + password: DELETE_CONFIRMATION, + }); + await expect(checkExistence('users', user._id)).to.eventually.eql(false); + }); + }); }); diff --git a/test/api/v3/integration/user/auth/DELETE-user_auth_social_network.test.js b/test/api/v3/integration/user/auth/DELETE-user_auth_social_network.test.js index 3f25404d50..12bef95459 100644 --- a/test/api/v3/integration/user/auth/DELETE-user_auth_social_network.test.js +++ b/test/api/v3/integration/user/auth/DELETE-user_auth_social_network.test.js @@ -95,4 +95,42 @@ describe('DELETE social registration', () => { expect(user.auth.goodl).to.be.undefined; }); }); + + context('Apple', () => { + it('fails if user does not have an alternative registration method', async () => { + await user.update({ + 'auth.apple.id': 'some-apple-id', + 'auth.local': { ok: true }, + }); + await expect(user.del('/user/auth/social/apple')).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('cantDetachSocial'), + }); + }); + + it('succeeds if user has a local registration', async () => { + await user.update({ + 'auth.apple.id': 'some-apple-id', + }); + + const response = await user.del('/user/auth/social/apple'); + expect(response).to.eql({}); + await user.sync(); + expect(user.auth.apple).to.be.undefined; + }); + + it('succeeds if user has a facebook registration', async () => { + await user.update({ + 'auth.apple.id': 'some-apple-id', + 'auth.facebook.id': 'some-facebook-id', + 'auth.local': { ok: true }, + }); + + const response = await user.del('/user/auth/social/apple'); + expect(response).to.eql({}); + await user.sync(); + expect(user.auth.goodl).to.be.undefined; + }); + }); }); diff --git a/test/api/v3/integration/user/auth/GET-user_auth_apple.test.js b/test/api/v3/integration/user/auth/GET-user_auth_apple.test.js new file mode 100644 index 0000000000..975fccfa3f --- /dev/null +++ b/test/api/v3/integration/user/auth/GET-user_auth_apple.test.js @@ -0,0 +1,50 @@ +import { + generateUser, + requester, + getProperty, +} from '../../../../../helpers/api-integration/v3'; +import * as appleAuth from '../../../../../../website/server/libs/auth/apple'; + +describe('GET /user/auth/apple', () => { + let api; + let user; + const appleEndpoint = '/user/auth/apple'; + + before(async () => { + const expectedResult = { id: 'appleId', name: 'an apple user' }; + sandbox.stub(appleAuth, 'appleProfile').returns(Promise.resolve(expectedResult)); + }); + + beforeEach(async () => { + api = requester(); + user = await generateUser(); + }); + + it('registers a new user', async () => { + const response = await api.get(appleEndpoint); + + expect(response.apiToken).to.exist; + expect(response.id).to.exist; + expect(response.newUser).to.be.true; + await expect(getProperty('users', response.id, 'auth.apple.id')).to.eventually.equal('appleId'); + await expect(getProperty('users', response.id, 'profile.name')).to.eventually.equal('an apple user'); + }); + + it('logs an existing user in', async () => { + const registerResponse = await api.get(appleEndpoint); + + const response = await api.get(appleEndpoint); + + expect(response.apiToken).to.eql(registerResponse.apiToken); + expect(response.id).to.eql(registerResponse.id); + expect(response.newUser).to.be.false; + }); + + it('add social auth to an existing user', async () => { + const response = await user.get(appleEndpoint); + + expect(response.apiToken).to.exist; + expect(response.id).to.exist; + expect(response.newUser).to.be.false; + }); +}); diff --git a/test/api/v3/integration/user/auth/POST-register_local.test.js b/test/api/v3/integration/user/auth/POST-register_local.test.js index e435d77dd0..b511ad0950 100644 --- a/test/api/v3/integration/user/auth/POST-register_local.test.js +++ b/test/api/v3/integration/user/auth/POST-register_local.test.js @@ -492,6 +492,74 @@ describe('POST /user/auth/local/register', () => { }); }); + context('attach to google user', () => { + let user; + const email = 'some@email-google.net'; + const username = 'some-username-google'; + const password = 'some-password'; + beforeEach(async () => { + user = await generateUser(); + }); + it('checks onlySocialAttachLocal', async () => { + await expect(user.post('/user/auth/local/register', { + email, + username, + password, + confirmPassword: password, + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('onlySocialAttachLocal'), + }); + }); + it('succeeds', async () => { + await user.update({ 'auth.google.id': 'some-google-id', 'auth.local': { ok: true } }); + await user.post('/user/auth/local/register', { + username, + email, + password, + confirmPassword: password, + }); + await user.sync(); + expect(user.auth.local.username).to.eql(username); + expect(user.auth.local.email).to.eql(email); + }); + }); + + context('attach to apple user', () => { + let user; + const email = 'some@email-apple.net'; + const username = 'some-username-apple'; + const password = 'some-password'; + beforeEach(async () => { + user = await generateUser(); + }); + it('checks onlySocialAttachLocal', async () => { + await expect(user.post('/user/auth/local/register', { + email, + username, + password, + confirmPassword: password, + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('onlySocialAttachLocal'), + }); + }); + it('succeeds', async () => { + await user.update({ 'auth.apple.id': 'some-apple-id', 'auth.local': { ok: true } }); + await user.post('/user/auth/local/register', { + username, + email, + password, + confirmPassword: password, + }); + await user.sync(); + expect(user.auth.local.username).to.eql(username); + expect(user.auth.local.email).to.eql(email); + }); + }); + context('login is already taken', () => { let username; let email; let api; diff --git a/test/api/v3/integration/user/auth/POST-user_auth_social.test.js b/test/api/v3/integration/user/auth/POST-user_auth_social.test.js index a934f3b8ed..eff5525603 100644 --- a/test/api/v3/integration/user/auth/POST-user_auth_social.test.js +++ b/test/api/v3/integration/user/auth/POST-user_auth_social.test.js @@ -51,6 +51,7 @@ describe('POST /user/auth/social', () => { await expect(getProperty('users', response.id, 'profile.name')).to.eventually.equal('a facebook user'); await expect(getProperty('users', response.id, 'auth.local.lowerCaseUsername')).to.exist; + await expect(getProperty('users', response.id, 'auth.facebook.id')).to.eventually.equal(facebookId); }); it('logs an existing user in', async () => { @@ -106,6 +107,7 @@ describe('POST /user/auth/social', () => { expect(response.apiToken).to.exist; expect(response.id).to.exist; expect(response.newUser).to.be.true; + await expect(getProperty('users', response.id, 'auth.google.id')).to.eventually.equal(googleId); await expect(getProperty('users', response.id, 'profile.name')).to.eventually.equal('a google user'); }); diff --git a/test/api/v4/user/auth/POST-register_local.test.js b/test/api/v4/user/auth/POST-register_local.test.js index 5d5b3abdbb..919b650e25 100644 --- a/test/api/v4/user/auth/POST-register_local.test.js +++ b/test/api/v4/user/auth/POST-register_local.test.js @@ -457,6 +457,74 @@ describe('POST /user/auth/local/register', () => { }); }); + context('attach to google user', () => { + let user; + const email = 'some-google@email.net'; + const username = 'some-username-google'; + const password = 'some-password'; + beforeEach(async () => { + user = await generateUser(); + }); + it('checks onlySocialAttachLocal', async () => { + await expect(user.post('/user/auth/local/register', { + email, + username, + password, + confirmPassword: password, + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('onlySocialAttachLocal'), + }); + }); + it('succeeds', async () => { + await user.update({ 'auth.google.id': 'some-google-id', 'auth.local': { ok: true } }); + await user.post('/user/auth/local/register', { + username, + email, + password, + confirmPassword: password, + }); + await user.sync(); + expect(user.auth.local.username).to.eql(username); + expect(user.auth.local.email).to.eql(email); + }); + }); + + context('attach to apple user', () => { + let user; + const email = 'some-apple@email.net'; + const username = 'some-username-apple'; + const password = 'some-password'; + beforeEach(async () => { + user = await generateUser(); + }); + it('checks onlySocialAttachLocal', async () => { + await expect(user.post('/user/auth/local/register', { + email, + username, + password, + confirmPassword: password, + })).to.eventually.be.rejected.and.eql({ + code: 401, + error: 'NotAuthorized', + message: t('onlySocialAttachLocal'), + }); + }); + it('succeeds', async () => { + await user.update({ 'auth.apple.id': 'some-apple-id', 'auth.local': { ok: true } }); + await user.post('/user/auth/local/register', { + username, + email, + password, + confirmPassword: password, + }); + await user.sync(); + expect(user.auth.local.username).to.eql(username); + expect(user.auth.local.email).to.eql(email); + }); + }); + context('login is already taken', () => { let username; let email; let api; diff --git a/website/client/src/assets/svg/apple.svg b/website/client/src/assets/svg/apple.svg new file mode 100644 index 0000000000..f1e31b85d9 --- /dev/null +++ b/website/client/src/assets/svg/apple.svg @@ -0,0 +1,4 @@ + + + + diff --git a/website/client/src/assets/svg/apple_black.svg b/website/client/src/assets/svg/apple_black.svg new file mode 100644 index 0000000000..8fd241fed4 --- /dev/null +++ b/website/client/src/assets/svg/apple_black.svg @@ -0,0 +1,55 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/website/client/src/components/auth/authForm.vue b/website/client/src/components/auth/authForm.vue index 993ed9cb10..bc8700526b 100644 --- a/website/client/src/components/auth/authForm.vue +++ b/website/client/src/components/auth/authForm.vue @@ -1,7 +1,7 @@