diff --git a/habitica-images b/habitica-images index 941bf731d6..fadca5c096 160000 --- a/habitica-images +++ b/habitica-images @@ -1 +1 @@ -Subproject commit 941bf731d61b21288e5453705d5718cae02f8af0 +Subproject commit fadca5c09661c51bd5d3c1ec3fd0ad0849f6ef6c diff --git a/package-lock.json b/package-lock.json index 227295a879..620da34974 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "habitica", - "version": "4.249.3", + "version": "4.250.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2038,9 +2038,9 @@ "integrity": "sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw==" }, "@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", "dev": true, "requires": { "type-detect": "4.0.8" @@ -2053,15 +2053,26 @@ "dev": true, "requires": { "@sinonjs/commons": "^1.7.0" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.5.tgz", + "integrity": "sha512-rTpCA0wG1wUxglBSFdMMY0oTrKYvgf4fNgv/sXbfCVAdf+FnPBdKJR/7XbpTCwbCrvCbdPYnlWaUUYz4V2fPDA==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + } } }, "@sinonjs/samsam": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.1.1.tgz", - "integrity": "sha512-cZ7rKJTLiE7u7Wi/v9Hc2fs3Ucc3jrWeMgPHbbTCeVAB2S0wOBbYlkJVeNSL04i7fdhT8wIbDq1zhC/PXTD2SA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-7.0.1.tgz", + "integrity": "sha512-zsAk2Jkiq89mhZovB2LLOdTCxJF4hqqTToGP0ASWlhp4I1hqOjcfmZGafXntCN7MDC6yySH0mFHrYtHceOeLmw==", "dev": true, "requires": { - "@sinonjs/commons": "^1.6.0", + "@sinonjs/commons": "^2.0.0", "lodash.get": "^4.4.2", "type-detect": "^4.0.8" } @@ -3067,7 +3078,7 @@ "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" }, "asn1": { "version": "0.2.4", @@ -4277,14 +4288,14 @@ "integrity": "sha512-z0R4cT5357OEAVkP1CEFTHz1egpu2gYiWm2WJOY/sQDhojEXUYL4m3v2kYi5wER3PkMRL+GgfDhed2kGzrHSZA==" }, "chai": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", - "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", + "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", "dev": true, "requires": { "assertion-error": "^1.1.0", "check-error": "^1.0.2", - "deep-eql": "^3.0.1", + "deep-eql": "^4.1.2", "get-func-name": "^2.0.0", "loupe": "^2.3.1", "pathval": "^1.1.1", @@ -5329,9 +5340,9 @@ } }, "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.2.tgz", + "integrity": "sha512-gT18+YW4CcW/DBNTwAmqTtkJh7f9qqScu2qFVlx7kCoeY9tlBu9cUcr7+I+Z/noG8INehS3xQgLpTtd/QUTn4w==", "dev": true, "requires": { "type-detect": "^4.0.0" @@ -5485,7 +5496,7 @@ "dezalgo": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", - "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", + "integrity": "sha512-K7i4zNfT2kgQz3GylDw40ot9GAE47sFZ9EXHFSPP6zONLgH6kWXE0KWJchkbQJLBkRazq4APwZ4OwiFFlT95OQ==", "requires": { "asap": "^2.0.0", "wrappy": "1" @@ -7570,7 +7581,7 @@ "get-func-name": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", "dev": true }, "get-intrinsic": { @@ -10130,7 +10141,7 @@ "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", "dev": true }, "lodash.includes": { @@ -10233,9 +10244,9 @@ } }, "loupe": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.1.tgz", - "integrity": "sha512-EN1D3jyVmaX4tnajVlfbREU4axL647hLec1h/PXAb8CPDMJiYitcWF2UeLVNttRqaIqQs4x+mRvXf+d+TlDrCA==", + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", "dev": true, "requires": { "get-func-name": "^2.0.0" @@ -11397,22 +11408,42 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, "nise": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.1.tgz", - "integrity": "sha512-yr5kW2THW1AkxVmCnKEh4nbYkJdB3I7LUkiUgOvEkOp414mc2UMaHMA7pjq1nYowhdoJZGwEKGaQVbxfpWj10A==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.2.tgz", + "integrity": "sha512-+gQjFi8v+tkfCuSCxfURHLhRhniE/+IaYbIphxAN2JRR9SHKhY8hgXpaXiYfHdw+gcGe4buxgbprBQFab9FkhA==", "dev": true, "requires": { - "@sinonjs/commons": "^1.8.3", - "@sinonjs/fake-timers": ">=5", + "@sinonjs/commons": "^2.0.0", + "@sinonjs/fake-timers": "^7.0.4", "@sinonjs/text-encoding": "^0.7.1", "just-extend": "^4.0.2", "path-to-regexp": "^1.7.0" }, "dependencies": { + "@sinonjs/fake-timers": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz", + "integrity": "sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.5.tgz", + "integrity": "sha512-rTpCA0wG1wUxglBSFdMMY0oTrKYvgf4fNgv/sXbfCVAdf+FnPBdKJR/7XbpTCwbCrvCbdPYnlWaUUYz4V2fPDA==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + } + } + }, "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", "dev": true }, "path-to-regexp": { @@ -12063,12 +12094,13 @@ "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" }, "passport": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/passport/-/passport-0.5.0.tgz", - "integrity": "sha512-ln+ue5YaNDS+fes6O5PCzXKSseY5u8MYhX9H5Co4s+HfYI5oqvnHKoOORLYDUPh+8tHvrxugF2GFcUA1Q1Gqfg==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.6.0.tgz", + "integrity": "sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==", "requires": { "passport-strategy": "1.x.x", - "pause": "0.0.1" + "pause": "0.0.1", + "utils-merge": "^1.0.1" } }, "passport-facebook": { @@ -12179,7 +12211,7 @@ "pause": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", - "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" }, "pause-stream": { "version": "0.0.11", @@ -12780,9 +12812,9 @@ } }, "regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==" + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" }, "regenerator-transform": { "version": "0.15.0", @@ -13595,16 +13627,16 @@ } }, "sinon": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-14.0.1.tgz", - "integrity": "sha512-JhJ0jCiyBWVAHDS+YSjgEbDn7Wgz9iIjA1/RK+eseJN0vAAWIWiXBdrnb92ELPyjsfreCYntD1ORtLSfIrlvSQ==", + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-14.0.2.tgz", + "integrity": "sha512-PDpV0ZI3ZCS3pEqx0vpNp6kzPhHrLx72wA0G+ZLaaJjLIYeE0n8INlgaohKuGy7hP0as5tbUd23QWu5U233t+w==", "dev": true, "requires": { - "@sinonjs/commons": "^1.8.3", + "@sinonjs/commons": "^2.0.0", "@sinonjs/fake-timers": "^9.1.2", - "@sinonjs/samsam": "^6.1.1", + "@sinonjs/samsam": "^7.0.1", "diff": "^5.0.0", - "nise": "^5.1.1", + "nise": "^5.1.2", "supports-color": "^7.2.0" }, "dependencies": { @@ -14283,9 +14315,9 @@ "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==" }, "superagent": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.0.2.tgz", - "integrity": "sha512-QtYZ9uaNAMexI7XWl2vAXAh0j4q9H7T0WVEI/y5qaUB3QLwxo+voUgCQ217AokJzUTIVOp0RTo7fhZrwhD7A2Q==", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.0.4.tgz", + "integrity": "sha512-yaQB7YqAHhutt8yU7XIRKXGXtkxKidVuiDJ8FJPBG7JPd7Ckv4X9cOdxVGH5DFsPXodsCrOSzdUi2xL+o9z1qw==", "requires": { "component-emitter": "^1.3.0", "cookiejar": "^2.1.3", @@ -14296,7 +14328,7 @@ "methods": "^1.1.2", "mime": "2.6.0", "qs": "^6.11.0", - "semver": "^7.3.7" + "semver": "^7.3.8" }, "dependencies": { "debug": { diff --git a/package.json b/package.json index abd7e3d727..77da8eec59 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "habitica", "description": "A habit tracker app which treats your goals like a Role Playing Game.", - "version": "4.249.3", + "version": "4.250.0", "main": "./website/server/index.js", "dependencies": { "@babel/core": "^7.19.6", @@ -54,7 +54,7 @@ "nconf": "^0.12.0", "node-gcm": "^1.0.5", "on-headers": "^1.0.2", - "passport": "^0.5.0", + "passport": "^0.6.0", "passport-facebook": "^3.0.0", "passport-google-oauth2": "^0.2.0", "passport-google-oauth20": "2.0.0", @@ -63,12 +63,12 @@ "ps-tree": "^1.0.0", "rate-limiter-flexible": "^2.4.0", "redis": "^3.1.2", - "regenerator-runtime": "^0.13.9", + "regenerator-runtime": "^0.13.11", "remove-markdown": "^0.5.0", "rimraf": "^3.0.2", "short-uuid": "^4.2.2", "stripe": "^10.13.0", - "superagent": "^8.0.2", + "superagent": "^8.0.4", "universal-analytics": "^0.5.3", "useragent": "^2.1.9", "uuid": "^8.3.2", @@ -111,7 +111,7 @@ }, "devDependencies": { "axios": "^0.27.2", - "chai": "^4.3.6", + "chai": "^4.3.7", "chai-as-promised": "^7.1.1", "chai-moment": "^0.1.0", "chalk": "^5.1.2", @@ -122,7 +122,7 @@ "monk": "^7.3.4", "require-again": "^2.0.0", "run-rs": "^0.7.7", - "sinon": "^14.0.1", + "sinon": "^14.0.2", "sinon-chai": "^3.7.0", "sinon-stub-promise": "^4.0.0" }, diff --git a/test/api/unit/libs/payments/apple.test.js b/test/api/unit/libs/payments/apple.test.js index e11c3bd86f..1aefe2cb51 100644 --- a/test/api/unit/libs/payments/apple.test.js +++ b/test/api/unit/libs/payments/apple.test.js @@ -218,7 +218,6 @@ describe('Apple Payments', () => { headers = {}; receipt = `{"token": "${token}"}`; nextPaymentProcessing = moment.utc().add({ days: 2 }); - user = new User(); iapSetupStub = sinon.stub(iap, 'setup') .resolves(); @@ -299,7 +298,6 @@ describe('Apple Payments', () => { expirationDate: moment.utc().add({ day: 1 }).toDate(), productId: option.sku, transactionId: token, - originalTransactionId: token, }]); sub = common.content.subscriptionBlocks[option.subKey]; @@ -323,116 +321,14 @@ describe('Apple Payments', () => { nextPaymentProcessing, }); }); - if (option !== subOptions[3]) { - const newOption = subOptions[3]; - it(`upgrades a subscription from ${option.sku} to ${newOption.sku}`, async () => { - const oldSub = common.content.subscriptionBlocks[option.subKey]; - user.profile.name = 'sender'; - user.purchased.plan.paymentMethod = applePayments.constants.PAYMENT_METHOD_APPLE; - user.purchased.plan.customerId = token; - user.purchased.plan.planId = option.subKey; - user.purchased.plan.additionalData = receipt; - iap.getPurchaseData.restore(); - iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') - .returns([{ - expirationDate: moment.utc().add({ day: 2 }).toDate(), - productId: newOption.sku, - transactionId: `${token}new`, - originalTransactionId: token, - }]); - sub = common.content.subscriptionBlocks[newOption.subKey]; - - await applePayments.subscribe(newOption.sku, - user, - receipt, - headers, - nextPaymentProcessing); - - expect(iapSetupStub).to.be.calledOnce; - expect(iapValidateStub).to.be.calledOnce; - expect(iapValidateStub).to.be.calledWith(iap.APPLE, receipt); - expect(iapIsValidatedStub).to.be.calledOnce; - expect(iapIsValidatedStub).to.be.calledWith({}); - expect(iapGetPurchaseDataStub).to.be.calledOnce; - - expect(paymentsCreateSubscritionStub).to.be.calledOnce; - expect(paymentsCreateSubscritionStub).to.be.calledWith({ - user, - customerId: token, - paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE, - sub, - headers, - additionalData: receipt, - nextPaymentProcessing, - updatedFrom: oldSub, - }); - }); - } - if (option !== subOptions[0]) { - const newOption = subOptions[0]; - it(`downgrades a subscription from ${option.sku} to ${newOption.sku}`, async () => { - const oldSub = common.content.subscriptionBlocks[option.subKey]; - user.profile.name = 'sender'; - user.purchased.plan.paymentMethod = applePayments.constants.PAYMENT_METHOD_APPLE; - user.purchased.plan.customerId = token; - user.purchased.plan.planId = option.subKey; - user.purchased.plan.additionalData = receipt; - iap.getPurchaseData.restore(); - iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') - .returns([{ - expirationDate: moment.utc().add({ day: 2 }).toDate(), - productId: newOption.sku, - transactionId: `${token}new`, - originalTransactionId: token, - }]); - sub = common.content.subscriptionBlocks[newOption.subKey]; - - await applePayments.subscribe(newOption.sku, - user, - receipt, - headers, - nextPaymentProcessing); - - expect(iapSetupStub).to.be.calledOnce; - expect(iapValidateStub).to.be.calledOnce; - expect(iapValidateStub).to.be.calledWith(iap.APPLE, receipt); - expect(iapIsValidatedStub).to.be.calledOnce; - expect(iapIsValidatedStub).to.be.calledWith({}); - expect(iapGetPurchaseDataStub).to.be.calledOnce; - - expect(paymentsCreateSubscritionStub).to.be.calledOnce; - expect(paymentsCreateSubscritionStub).to.be.calledWith({ - user, - customerId: token, - paymentMethod: applePayments.constants.PAYMENT_METHOD_APPLE, - sub, - headers, - additionalData: receipt, - nextPaymentProcessing, - updatedFrom: oldSub, - }); - }); - } }); - it('errors when a user is using the same subscription', async () => { - user = new User(); - user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate(); - await user.save(); + it('errors when a user is already subscribed', async () => { payments.createSubscription.restore(); - - iap.getPurchaseData.restore(); - iapGetPurchaseDataStub = sinon.stub(iap, 'getPurchaseData') - .returns([{ - expirationDate: moment.utc().add({ day: 1 }).toDate(), - productId: sku, - transactionId: token, - originalTransactionId: token, - }]); + user = new User(); + await user.save(); await applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing); - user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate(); - await user.save(); await expect(applePayments.subscribe(sku, user, receipt, headers, nextPaymentProcessing)) .to.eventually.be.rejected.and.to.eql({ diff --git a/test/api/unit/libs/payments/payments.test.js b/test/api/unit/libs/payments/payments.test.js index 16af9e6e15..65b42b42f3 100644 --- a/test/api/unit/libs/payments/payments.test.js +++ b/test/api/unit/libs/payments/payments.test.js @@ -370,10 +370,6 @@ describe('payments/index', () => { }); context('Purchasing a subscription for self', () => { - beforeEach(() => { - data.user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate(); - }); - it('creates a subscription', async () => { expect(user.purchased.plan.planId).to.not.exist; @@ -400,7 +396,6 @@ describe('payments/index', () => { user.purchased.plan = plan; user.purchased.plan.dateTerminated = moment(new Date()).add(2, 'months'); expect(user.purchased.plan.extraMonths).to.eql(0); - data.user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate(); await api.createSubscription(data); @@ -411,7 +406,6 @@ describe('payments/index', () => { user.purchased.plan = plan; user.purchased.plan.dateTerminated = moment(new Date()).subtract(2, 'months'); expect(user.purchased.plan.extraMonths).to.eql(0); - data.user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate(); await api.createSubscription(data); @@ -421,7 +415,6 @@ describe('payments/index', () => { it('does not reset Gold-to-Gems cap on additional subscription', async () => { user.purchased.plan = plan; user.purchased.plan.gemsBought = 10; - data.user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate(); await api.createSubscription(data); @@ -472,96 +465,9 @@ describe('payments/index', () => { }, }); }); - - context('Upgrades subscription', () => { - it('from basic_earned to basic_6mo', async () => { - data.sub.key = 'basic_earned'; - expect(user.purchased.plan.planId).to.not.exist; - - await api.createSubscription(data); - - expect(user.purchased.plan.planId).to.eql('basic_earned'); - expect(user.purchased.plan.customerId).to.eql('customer-id'); - const created = user.purchased.plan.dateCreated; - const updated = user.purchased.plan.dateUpdated; - - data.sub.key = 'basic_6mo'; - data.updatedFrom = { key: 'basic_earned' }; - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.dateCreated).to.eql(created); - expect(user.purchased.plan.dateUpdated).to.not.eql(updated); - expect(user.purchased.plan.customerId).to.eql('customer-id'); - }); - - it('from basic_3mo to basic_12mo', async () => { - expect(user.purchased.plan.planId).to.not.exist; - - await api.createSubscription(data); - - expect(user.purchased.plan.planId).to.eql('basic_3mo'); - expect(user.purchased.plan.customerId).to.eql('customer-id'); - const created = user.purchased.plan.dateCreated; - const updated = user.purchased.plan.dateUpdated; - - data.sub.key = 'basic_12mo'; - data.updatedFrom = { key: 'basic_3mo' }; - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.dateCreated).to.eql(created); - expect(user.purchased.plan.dateUpdated).to.not.eql(updated); - expect(user.purchased.plan.customerId).to.eql('customer-id'); - }); - }); - - context('Downgrades subscription', () => { - it('from basic_6mo to basic_earned', async () => { - data.sub.key = 'basic_6mo'; - expect(user.purchased.plan.planId).to.not.exist; - - await api.createSubscription(data); - - expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.customerId).to.eql('customer-id'); - const created = user.purchased.plan.dateCreated; - const updated = user.purchased.plan.dateUpdated; - - data.sub.key = 'basic_earned'; - data.updatedFrom = { key: 'basic_6mo' }; - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.eql('basic_earned'); - expect(user.purchased.plan.dateCreated).to.eql(created); - expect(user.purchased.plan.dateUpdated).to.not.eql(updated); - expect(user.purchased.plan.customerId).to.eql('customer-id'); - }); - - it('from basic_12mo to basic_3mo', async () => { - expect(user.purchased.plan.planId).to.not.exist; - - data.sub.key = 'basic_12mo'; - await api.createSubscription(data); - - expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.customerId).to.eql('customer-id'); - const created = user.purchased.plan.dateCreated; - const updated = user.purchased.plan.dateUpdated; - - data.sub.key = 'basic_3mo'; - data.updatedFrom = { key: 'basic_12mo' }; - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.eql('basic_3mo'); - expect(user.purchased.plan.dateCreated).to.eql(created); - expect(user.purchased.plan.dateUpdated).to.not.eql(updated); - expect(user.purchased.plan.customerId).to.eql('customer-id'); - }); - }); }); context('Block subscription perks', () => { - beforeEach(() => { - data.user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate(); - }); - it('adds block months to plan.consecutive.offset', async () => { await api.createSubscription(data); @@ -582,6 +488,7 @@ describe('payments/index', () => { it('adds 10 to plan.consecutive.gemCapExtra for 6 month block', async () => { data.sub.key = 'basic_6mo'; + await api.createSubscription(data); expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10); @@ -589,6 +496,7 @@ describe('payments/index', () => { it('adds 20 to plan.consecutive.gemCapExtra for 12 month block', async () => { data.sub.key = 'basic_12mo'; + await api.createSubscription(data); expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20); @@ -598,7 +506,6 @@ describe('payments/index', () => { data.sub.key = 'basic_12mo'; await api.createSubscription(data); - data.user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate(); await api.createSubscription(data); expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(25); @@ -625,134 +532,6 @@ describe('payments/index', () => { expect(user.purchased.plan.consecutive.trinkets).to.eql(4); }); - - context('Upgrades subscription', () => { - it('Adds 10 to plan.consecutive.gemCapExtra from basic_earned to basic_6mo', async () => { - data.sub.key = 'basic_earned'; - expect(user.purchased.plan.planId).to.not.exist; - - await api.createSubscription(data); - - expect(user.purchased.plan.planId).to.eql('basic_earned'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(0); - - data.sub.key = 'basic_6mo'; - data.updatedFrom = { key: 'basic_earned' }; - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10); - }); - - it('Adds 15 to plan.consecutive.gemCapExtra when upgrading from basic_3mo to basic_12mo', async () => { - expect(user.purchased.plan.planId).to.not.exist; - - await api.createSubscription(data); - - expect(user.purchased.plan.planId).to.eql('basic_3mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(5); - - data.sub.key = 'basic_12mo'; - data.updatedFrom = { key: 'basic_3mo' }; - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20); - }); - - it('Adds 2 to plan.consecutive.trinkets from basic_earned to basic_6mo', async () => { - data.sub.key = 'basic_earned'; - expect(user.purchased.plan.planId).to.not.exist; - - await api.createSubscription(data); - - expect(user.purchased.plan.planId).to.eql('basic_earned'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(0); - - data.sub.key = 'basic_6mo'; - data.updatedFrom = { key: 'basic_earned' }; - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); - }); - - it('Adds 3 to plan.consecutive.trinkets when upgrading from basic_3mo to basic_12mo', async () => { - expect(user.purchased.plan.planId).to.not.exist; - - await api.createSubscription(data); - - expect(user.purchased.plan.planId).to.eql('basic_3mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(1); - - data.sub.key = 'basic_12mo'; - data.updatedFrom = { key: 'basic_3mo' }; - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(4); - }); - }); - - context('Downgrades subscription', () => { - it('does not remove from plan.consecutive.gemCapExtra from basic_6mo to basic_earned', async () => { - data.sub.key = 'basic_6mo'; - expect(user.purchased.plan.planId).to.not.exist; - - await api.createSubscription(data); - - expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10); - - data.sub.key = 'basic_earned'; - data.updatedFrom = { key: 'basic_6mo' }; - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.eql('basic_earned'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(10); - }); - - it('does not remove from plan.consecutive.gemCapExtra from basic_12mo to basic_3mo', async () => { - expect(user.purchased.plan.planId).to.not.exist; - - data.sub.key = 'basic_12mo'; - await api.createSubscription(data); - - expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20); - - data.sub.key = 'basic_3mo'; - data.updatedFrom = { key: 'basic_12mo' }; - await api.createSubscription(data); - expect(user.purchased.plan.consecutive.gemCapExtra).to.eql(20); - }); - - it('does not remove from plan.consecutive.trinkets from basic_6mo to basic_earned', async () => { - data.sub.key = 'basic_6mo'; - expect(user.purchased.plan.planId).to.not.exist; - - await api.createSubscription(data); - - expect(user.purchased.plan.planId).to.eql('basic_6mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); - - data.sub.key = 'basic_earned'; - data.updatedFrom = { key: 'basic_6mo' }; - await api.createSubscription(data); - expect(user.purchased.plan.planId).to.eql('basic_earned'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(2); - }); - - it('does not remove from plan.consecutive.trinkets from basic_12mo to basic_3mo', async () => { - expect(user.purchased.plan.planId).to.not.exist; - - data.sub.key = 'basic_12mo'; - await api.createSubscription(data); - - expect(user.purchased.plan.planId).to.eql('basic_12mo'); - expect(user.purchased.plan.consecutive.trinkets).to.eql(4); - - data.sub.key = 'basic_3mo'; - data.updatedFrom = { key: 'basic_12mo' }; - await api.createSubscription(data); - expect(user.purchased.plan.consecutive.trinkets).to.eql(4); - }); - }); }); context('Mystery Items', () => { @@ -765,7 +544,6 @@ describe('payments/index', () => { now: mayMysteryItemTimeframe, toFake: ['Date'], }); - data.user.purchased.plan.dateUpdated = moment().subtract(1, 'hours').toDate(); }); afterEach(() => { 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 b511ad0950..6e548051cd 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 @@ -344,6 +344,24 @@ describe('POST /user/auth/local/register', () => { }); }); + it('enforces maximum length for the password', async () => { + const username = generateRandomUserName(); + const email = `${username}@example.com`; + const password = '12345678910111213141516171819202122232425262728293031323334353637383940'; + const confirmPassword = '12345678910111213141516171819202122232425262728293031323334353637383940'; + + await expect(api.post('/user/auth/local/register', { + username, + email, + password, + confirmPassword, + })).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('invalidReqParams'), + }); + }); + it('requires a username', async () => { const email = `${generateRandomUserName()}@example.com`; const password = 'password'; diff --git a/website/client/package-lock.json b/website/client/package-lock.json index b80407f8ae..5e74b91056 100644 --- a/website/client/package-lock.json +++ b/website/client/package-lock.json @@ -17072,9 +17072,9 @@ } }, "dompurify": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.0.tgz", - "integrity": "sha512-Be9tbQMZds4a3C6xTmz68NlMfeONA//4dOavl/1rNw50E+/QO0KVpbcU0PcaW0nsQxurXls9ZocqFxk8R2mWEA==" + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.1.tgz", + "integrity": "sha512-ewwFzHzrrneRjxzmK6oVz/rZn9VWspGFRDb4/rRtIsM1n36t9AKma/ye8syCpcw+XJ25kOK/hOG7t1j2I2yBqA==" }, "domutils": { "version": "1.7.0", @@ -28824,7 +28824,6 @@ "version": "npm:vue-loader@16.8.3", "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-16.8.3.tgz", "integrity": "sha512-7vKN45IxsKxe5GcVCbc2qFU5aWzyiLrYJyUuMz4BQLKctCj/fmCa0w6fGiiQ2cLFetNcek1ppGJQDCup0c1hpA==", - "optional": true, "requires": { "chalk": "^4.1.0", "hash-sum": "^2.0.0", @@ -28835,7 +28834,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "optional": true, "requires": { "color-convert": "^2.0.1" } @@ -28844,7 +28842,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "optional": true, "requires": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -28854,7 +28851,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "optional": true, "requires": { "color-name": "~1.1.4" } @@ -28862,26 +28858,22 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "optional": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "emojis-list": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "optional": true + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==" }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "optional": true + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, "loader-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.3.tgz", - "integrity": "sha512-THWqIsn8QRnvLl0shHYVBN9syumU8pYWEHPTmkiVGd+7K5eFNVSY6AJhRvgGF70gg1Dz+l/k8WicvFCxdEs60A==", - "optional": true, + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", "requires": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", @@ -28892,7 +28884,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "optional": true, "requires": { "has-flag": "^4.0.0" } diff --git a/website/client/package.json b/website/client/package.json index 512d23fa0d..58fd3cfa79 100644 --- a/website/client/package.json +++ b/website/client/package.json @@ -33,7 +33,7 @@ "bootstrap-vue": "^2.22.0", "chai": "^4.3.6", "core-js": "^3.26.0", - "dompurify": "^2.4.0", + "dompurify": "^2.4.1", "eslint": "^6.8.0", "eslint-config-habitrpg": "^6.2.0", "eslint-plugin-mocha": "^5.3.0", @@ -49,6 +49,7 @@ "sass": "^1.34.0", "sass-loader": "^8.0.2", "smartbanner.js": "^1.19.1", + "stopword": "^2.0.5", "svg-inline-loader": "^0.8.2", "svg-url-loader": "^7.1.1", "svgo": "^1.3.2", diff --git a/website/client/src/assets/css/sprites/spritesmith-main.css b/website/client/src/assets/css/sprites/spritesmith-main.css index 8d02728540..508333b2df 100644 --- a/website/client/src/assets/css/sprites/spritesmith-main.css +++ b/website/client/src/assets/css/sprites/spritesmith-main.css @@ -27415,6 +27415,46 @@ width: 114px; height: 90px; } +.broad_armor_mystery_202212 { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_mystery_202212.png'); + width: 114px; + height: 90px; +} +.headAccessory_mystery_202212 { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/headAccessory_mystery_202212.png'); + width: 114px; + height: 90px; +} +.shop_armor_mystery_202212 { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_armor_mystery_202212.png'); + width: 68px; + height: 68px; +} +.shop_headAccessory_mystery_202212 { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_headAccessory_mystery_202212.png'); + width: 68px; + height: 68px; +} +.shop_set_mystery_202212 { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_set_mystery_202212.png'); + width: 68px; + height: 68px; +} +.shop_weapon_mystery_202212 { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_weapon_mystery_202212.png'); + width: 68px; + height: 68px; +} +.slim_armor_mystery_202212 { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_mystery_202212.png'); + width: 114px; + height: 90px; +} +.weapon_mystery_202212 { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_mystery_202212.png'); + width: 114px; + height: 90px; +} .broad_armor_mystery_301404 { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_mystery_301404.png'); width: 90px; diff --git a/website/client/src/assets/svg/check.svg b/website/client/src/assets/svg/check.svg index 2ae3d23949..0d9eb368fc 100644 --- a/website/client/src/assets/svg/check.svg +++ b/website/client/src/assets/svg/check.svg @@ -1,3 +1,3 @@ - - + + \ No newline at end of file diff --git a/website/client/src/components/inventory/stable/index.vue b/website/client/src/components/inventory/stable/index.vue index 9e1cedc34c..18f4ef1a6e 100644 --- a/website/client/src/components/inventory/stable/index.vue +++ b/website/client/src/components/inventory/stable/index.vue @@ -787,18 +787,15 @@ export default { if (sortBy === 'sortByColor') { groupKey = 'potionKey'; } else if (sortBy === 'AZ') { - groupKey = ''; + groupKey = i => i.eggName[0]; } else if (sortBy === 'sortByHatchable') { groupKey = i => (i.isHatchable() ? 0 : 1); } const groupedPets = groupBy(pets, groupKey); // Pets are rendered as grouped "rows". Count helps decide if show more button is necessary. - if (sortBy === 'AZ') { - this.petRowCount[animalGroup.key] = 1; - } else { - this.petRowCount[animalGroup.key] = Object.keys(groupedPets).length; - } + this.petRowCount[animalGroup.key] = Object.keys(groupedPets).length; + return groupedPets; }, mounts (animalGroup, hideMissing, sortBy, searchText) { @@ -814,14 +811,12 @@ export default { if (sortBy === 'sortByColor') { groupKey = 'potionKey'; } else if (sortBy === 'AZ') { - groupKey = ''; + groupKey = i => i.eggName[0]; } const groupedMounts = groupBy(mounts, groupKey); - if (sortBy === 'AZ') { - this.mountRowCount[animalGroup.key] = 1; - } else { - this.mountRowCount[animalGroup.key] = Object.keys(groupedMounts).length; - } + + this.mountRowCount[animalGroup.key] = Object.keys(groupedMounts).length; + return groupedMounts; }, // Actions diff --git a/website/client/src/components/payments/buyGemsModal.vue b/website/client/src/components/payments/buyGemsModal.vue index 53647ebff8..3c4c0ff568 100644 --- a/website/client/src/components/payments/buyGemsModal.vue +++ b/website/client/src/components/payments/buyGemsModal.vue @@ -359,8 +359,8 @@ .svg-icon.check { color: $purple-400; - width: 0.77rem; - height: 0.615rem; + width: 16px; + height: 16px; } .text-leadin { diff --git a/website/client/src/components/shops/buyModal.vue b/website/client/src/components/shops/buyModal.vue index e5ac69e072..3def50268b 100644 --- a/website/client/src/components/shops/buyModal.vue +++ b/website/client/src/components/shops/buyModal.vue @@ -380,7 +380,6 @@ import EquipmentAttributesGrid from '../inventory/equipment/attributesGrid.vue'; import Item from '@/components/inventory/item'; import Avatar from '@/components/avatar'; -import seasonalShopConfig from '@/../../common/script/libs/shops-seasonal.config'; import { drops as dropEggs } from '@/../../common/script/content/eggs'; import { drops as dropPotions } from '@/../../common/script/content/hatching-potions'; @@ -437,7 +436,6 @@ export default { selectedAmountToBuy: 1, isPinned: false, - endDate: seasonalShopConfig.dateRange.end, }; }, computed: { @@ -488,6 +486,9 @@ export default { nonSubscriberHourglasses () { return (!this.user.purchased.plan.customerId && !this.user.purchased.plan.consecutive.trinkets && this.getPriceClass() === 'hourglasses'); }, + endDate () { + return moment(this.item.event.end); + }, }, watch: { item: function itemChanged () { diff --git a/website/client/src/components/shops/market/categoryRow.vue b/website/client/src/components/shops/market/categoryRow.vue index eada1073a0..77610d2ae2 100644 --- a/website/client/src/components/shops/market/categoryRow.vue +++ b/website/client/src/components/shops/market/categoryRow.vue @@ -8,16 +8,6 @@ :popover-position="'top'" @click="itemSelected(item)" > - - {{ $t('maxBuyGems') }} -

{{ item.text }}

-
- {{ limitedString }} -
-