diff --git a/package.json b/package.json index 3fa0b4e..6096f10 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,9 @@ "db:seed": "tsx prisma/seed.ts" }, "dependencies": { + "@alicloud/dysmsapi20170525": "^4.5.1", + "@alicloud/openapi-client": "^0.4.15", + "@alicloud/tea-util": "^1.4.11", "@auth/prisma-adapter": "^2.11.2", "@prisma/client": "^6.19.3", "clsx": "^2.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 35ee602..87cf4b9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,15 @@ importers: .: dependencies: + '@alicloud/dysmsapi20170525': + specifier: ^4.5.1 + version: 4.5.1 + '@alicloud/openapi-client': + specifier: ^0.4.15 + version: 0.4.15 + '@alicloud/tea-util': + specifier: ^1.4.11 + version: 1.4.11 '@auth/prisma-adapter': specifier: ^2.11.2 version: 2.11.2(@prisma/client@6.19.3(prisma@6.19.3(typescript@6.0.3))(typescript@6.0.3)) @@ -84,6 +93,60 @@ importers: packages: + '@alicloud/credentials@2.4.4': + resolution: {integrity: sha512-/eRAGSKcniLIFQ1UCpDhB/IrHUZisQ1sc65ws/c2avxUMpXwH1rWAohb76SVAUJhiF4mwvLzLJM1Mn1XL4Xe/Q==} + + '@alicloud/darabonba-array@0.1.2': + resolution: {integrity: sha512-ZPuQ+bJyjrd8XVVm55kl+ypk7OQoi1ZH/DiToaAEQaGvgEjrTcvQkg71//vUX/6cvbLIF5piQDvhrLb+lUEIPQ==} + + '@alicloud/darabonba-encode-util@0.0.1': + resolution: {integrity: sha512-Sl5vCRVAYMqwmvXpJLM9hYoCHOMsQlGxaWSGhGWulpKk/NaUBArtoO1B0yHruJf1C5uHhEJIaylYcM48icFHgw==} + + '@alicloud/darabonba-encode-util@0.0.2': + resolution: {integrity: sha512-mlsNctkeqmR0RtgE1Rngyeadi5snLOAHBCWEtYf68d7tyKskosXDTNeZ6VCD/UfrUu4N51ItO8zlpfXiOgeg3A==} + + '@alicloud/darabonba-map@0.0.1': + resolution: {integrity: sha512-2ep+G3YDvuI+dRYVlmER1LVUQDhf9kEItmVB/bbEu1pgKzelcocCwAc79XZQjTcQGFgjDycf3vH87WLDGLFMlw==} + + '@alicloud/darabonba-signature-util@0.0.4': + resolution: {integrity: sha512-I1TtwtAnzLamgqnAaOkN0IGjwkiti//0a7/auyVThdqiC/3kyafSAn6znysWOmzub4mrzac2WiqblZKFcN5NWg==} + + '@alicloud/darabonba-string@1.0.3': + resolution: {integrity: sha512-NyWwrU8cAIesWk3uHL1Q7pTDTqLkCI/0PmJXC4/4A0MFNAZ9Ouq0iFBsRqvfyUujSSM+WhYLuTfakQXiVLkTMA==} + + '@alicloud/dysmsapi20170525@4.5.1': + resolution: {integrity: sha512-6l0N6M+uV2l+KxF5XOiZ3DZoDlrZ2ZxFDKkr424KJm0A6kSsQlRNV5eiJoMkB6h11KYf0BD5/0lXzW8SkPtGMA==} + + '@alicloud/endpoint-util@0.0.1': + resolution: {integrity: sha512-+pH7/KEXup84cHzIL6UJAaPqETvln4yXlD9JzlrqioyCSaWxbug5FUobsiI6fuUOpw5WwoB3fWAtGbFnJ1K3Yg==} + + '@alicloud/gateway-pop@0.0.6': + resolution: {integrity: sha512-KF4I+JvfYuLKc3fWeWYIZ7lOVJ9jRW0sQXdXidZn1DKZ978ncfGf7i0LBfONGk4OxvNb/HD3/0yYhkgZgPbKtA==} + + '@alicloud/gateway-spi@0.0.8': + resolution: {integrity: sha512-KM7fu5asjxZPmrz9sJGHJeSU+cNQNOxW+SFmgmAIrITui5hXL2LB+KNRuzWmlwPjnuA2X3/keq9h6++S9jcV5g==} + + '@alicloud/openapi-client@0.4.15': + resolution: {integrity: sha512-4VE0/k5ZdQbAhOSTqniVhuX1k5DUeUMZv74degn3wIWjLY6Bq+hxjaGsaHYlLZ2gA5wUrs8NcI5TE+lIQS3iiA==} + + '@alicloud/openapi-core@1.0.7': + resolution: {integrity: sha512-I80PQVfmlzRiXGHwutMp2zTpiqUVv8ts30nWAfksfHUSTIapk3nj9IXaPbULMPGNV6xqEyshO2bj2a+pmwc2tQ==} + + '@alicloud/openapi-util@0.3.3': + resolution: {integrity: sha512-vf0cQ/q8R2U7ZO88X5hDiu1yV3t/WexRj+YycWxRutkH/xVXfkmpRgps8lmNEk7Ar+0xnY8+daN2T+2OyB9F4A==} + + '@alicloud/tea-typescript@1.8.0': + resolution: {integrity: sha512-CWXWaquauJf0sW30mgJRVu9aaXyBth5uMBCUc+5vKTK1zlgf3hIqRUjJZbjlwHwQ5y9anwcu18r48nOZb7l2QQ==} + + '@alicloud/tea-util@1.4.11': + resolution: {integrity: sha512-HyPEEQ8F0WoZegiCp7sVdrdm6eBOB+GCvGl4182u69LDFktxfirGLcAx3WExUr1zFWkq2OSmBroTwKQ4w/+Yww==} + + '@alicloud/tea-util@1.4.9': + resolution: {integrity: sha512-S0wz76rGtoPKskQtRTGqeuqBHFj8BqUn0Vh+glXKun2/9UpaaaWmuJwcmtImk6bJZfLYEShDF/kxDmDJoNYiTw==} + + '@alicloud/tea-xml@0.0.3': + resolution: {integrity: sha512-+/9GliugjrLglsXVrd1D80EqqKgGpyA0eQ6+1ZdUOYCaRguaSwz44trX3PaxPu/HhIPJg9PsGQQ3cSLXWZjbAA==} + '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} @@ -174,6 +237,9 @@ packages: resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} + '@darabonba/typescript@1.0.4': + resolution: {integrity: sha512-icl8RGTw4DiWRpco6dVh21RS0IqrH4s/eEV36TZvz/e1+paogSZjaAgox7ByrlEuvG+bo5d8miq/dRlqiUaL/w==} + '@emnapi/core@1.10.0': resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} @@ -800,9 +866,15 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/node@12.0.2': + resolution: {integrity: sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA==} + '@types/node@20.19.40': resolution: {integrity: sha512-xxx6M2IpSTnnKcR0cMvIiohkiCx20/oRPtWGbenFygKCGl3zqUzdNjQ/1V4solq1LU+dgv0nQzeGOuqkqZGg0Q==} + '@types/node@22.19.19': + resolution: {integrity: sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==} + '@types/react-dom@19.2.3': resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: @@ -811,6 +883,9 @@ packages: '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + '@types/xml2js@0.4.14': + resolution: {integrity: sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==} + '@typescript-eslint/eslint-plugin@8.59.2': resolution: {integrity: sha512-j/bwmkBvHUtPNxzuWe5z6BEk3q54YRyGlBXkSsmfoih7zNrBvl5A9A98anlp/7JbyZcWIJ8KXo/3Tq/DjFLtuQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1616,6 +1691,9 @@ packages: hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + httpx@2.3.3: + resolution: {integrity: sha512-k1qv94u1b6e+XKCxVbLgYlOypVP9MPGpnN5G/vxFf6tDO4V3xpz3d6FUOY/s8NtPgaq5RBVVgSB+7IHpVxMYzw==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1632,6 +1710,10 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + ini@1.3.5: + resolution: {integrity: sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==} + deprecated: Please update to ini >=1.3.6 to avoid a prototype pollution issue + internal-slot@1.1.0: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} @@ -1808,6 +1890,9 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + kitx@2.2.0: + resolution: {integrity: sha512-tBMwe6AALTBQJb0woQDD40734NKzb0Kzi3k7wQj9ar3AbP9oqhoVrdXPh7rk2r00/glIgd0YbToIUJsnxWMiIg==} + language-subtag-registry@0.3.20: resolution: {integrity: sha512-KPMwROklF4tEx283Xw0pNKtfTj1gZ4UByp4EsIFWLgBavJltF4TiYPc39k06zSTsLzxTVXXDSpbwaQXaFB4Qeg==} @@ -1906,6 +1991,9 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -1955,6 +2043,12 @@ packages: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} + moment-timezone@0.5.45: + resolution: {integrity: sha512-HIWmqA86KcmCAhnMAN0wuDOARV/525R2+lOLotuGFzn4HO+FH+/645z2wx0Dt3iDv6/p61SIvKnDstISainhLQ==} + + moment@2.30.1: + resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} + motion-dom@12.38.0: resolution: {integrity: sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==} @@ -2254,6 +2348,10 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} + sax@1.6.0: + resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} + engines: {node: '>=11.0.0'} + scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} @@ -2310,6 +2408,9 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + sm3@1.0.3: + resolution: {integrity: sha512-KyFkIfr8QBlFG3uc3NaljaXdYcsbRy1KrSfc4tsQV8jW68jAktGeOcifu530Vx/5LC+PULHT0Rv8LiI8Gw+c1g==} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -2515,6 +2616,14 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} + xml2js@0.6.2: + resolution: {integrity: sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==} + engines: {node: '>=4.0.0'} + + xmlbuilder@11.0.1: + resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} + engines: {node: '>=4.0'} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -2547,6 +2656,146 @@ packages: snapshots: + '@alicloud/credentials@2.4.4': + dependencies: + '@alicloud/tea-typescript': 1.8.0 + httpx: 2.3.3 + ini: 1.3.5 + kitx: 2.2.0 + transitivePeerDependencies: + - supports-color + + '@alicloud/darabonba-array@0.1.2': + dependencies: + '@alicloud/tea-typescript': 1.8.0 + transitivePeerDependencies: + - supports-color + + '@alicloud/darabonba-encode-util@0.0.1': + dependencies: + '@alicloud/tea-typescript': 1.8.0 + moment: 2.30.1 + transitivePeerDependencies: + - supports-color + + '@alicloud/darabonba-encode-util@0.0.2': + dependencies: + moment: 2.30.1 + + '@alicloud/darabonba-map@0.0.1': + dependencies: + '@alicloud/tea-typescript': 1.8.0 + transitivePeerDependencies: + - supports-color + + '@alicloud/darabonba-signature-util@0.0.4': + dependencies: + '@alicloud/darabonba-encode-util': 0.0.1 + transitivePeerDependencies: + - supports-color + + '@alicloud/darabonba-string@1.0.3': + dependencies: + '@alicloud/tea-typescript': 1.8.0 + transitivePeerDependencies: + - supports-color + + '@alicloud/dysmsapi20170525@4.5.1': + dependencies: + '@alicloud/openapi-core': 1.0.7 + '@darabonba/typescript': 1.0.4 + transitivePeerDependencies: + - supports-color + + '@alicloud/endpoint-util@0.0.1': + dependencies: + '@alicloud/tea-typescript': 1.8.0 + kitx: 2.2.0 + transitivePeerDependencies: + - supports-color + + '@alicloud/gateway-pop@0.0.6': + dependencies: + '@alicloud/credentials': 2.4.4 + '@alicloud/darabonba-array': 0.1.2 + '@alicloud/darabonba-encode-util': 0.0.2 + '@alicloud/darabonba-map': 0.0.1 + '@alicloud/darabonba-signature-util': 0.0.4 + '@alicloud/darabonba-string': 1.0.3 + '@alicloud/endpoint-util': 0.0.1 + '@alicloud/gateway-spi': 0.0.8 + '@alicloud/openapi-util': 0.3.3 + '@alicloud/tea-typescript': 1.8.0 + '@alicloud/tea-util': 1.4.11 + transitivePeerDependencies: + - supports-color + + '@alicloud/gateway-spi@0.0.8': + dependencies: + '@alicloud/credentials': 2.4.4 + '@alicloud/tea-typescript': 1.8.0 + transitivePeerDependencies: + - supports-color + + '@alicloud/openapi-client@0.4.15': + dependencies: + '@alicloud/credentials': 2.4.4 + '@alicloud/gateway-spi': 0.0.8 + '@alicloud/openapi-util': 0.3.3 + '@alicloud/tea-typescript': 1.8.0 + '@alicloud/tea-util': 1.4.9 + '@alicloud/tea-xml': 0.0.3 + transitivePeerDependencies: + - supports-color + + '@alicloud/openapi-core@1.0.7': + dependencies: + '@alicloud/credentials': 2.4.4 + '@alicloud/gateway-pop': 0.0.6 + '@alicloud/gateway-spi': 0.0.8 + '@darabonba/typescript': 1.0.4 + transitivePeerDependencies: + - supports-color + + '@alicloud/openapi-util@0.3.3': + dependencies: + '@alicloud/tea-typescript': 1.8.0 + '@alicloud/tea-util': 1.4.11 + kitx: 2.2.0 + sm3: 1.0.3 + transitivePeerDependencies: + - supports-color + + '@alicloud/tea-typescript@1.8.0': + dependencies: + '@types/node': 12.0.2 + httpx: 2.3.3 + transitivePeerDependencies: + - supports-color + + '@alicloud/tea-util@1.4.11': + dependencies: + '@alicloud/tea-typescript': 1.8.0 + '@darabonba/typescript': 1.0.4 + kitx: 2.2.0 + transitivePeerDependencies: + - supports-color + + '@alicloud/tea-util@1.4.9': + dependencies: + '@alicloud/tea-typescript': 1.8.0 + kitx: 2.2.0 + transitivePeerDependencies: + - supports-color + + '@alicloud/tea-xml@0.0.3': + dependencies: + '@alicloud/tea-typescript': 1.8.0 + '@types/xml2js': 0.4.14 + xml2js: 0.6.2 + transitivePeerDependencies: + - supports-color + '@alloc/quick-lru@5.2.0': {} '@auth/core@0.41.2': @@ -2666,6 +2915,17 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@darabonba/typescript@1.0.4': + dependencies: + '@alicloud/tea-typescript': 1.8.0 + httpx: 2.3.3 + lodash: 4.18.1 + moment: 2.30.1 + moment-timezone: 0.5.45 + xml2js: 0.6.2 + transitivePeerDependencies: + - supports-color + '@emnapi/core@1.10.0': dependencies: '@emnapi/wasi-threads': 1.2.1 @@ -3128,10 +3388,16 @@ snapshots: '@types/json5@0.0.29': {} + '@types/node@12.0.2': {} + '@types/node@20.19.40': dependencies: undici-types: 6.21.0 + '@types/node@22.19.19': + dependencies: + undici-types: 6.21.0 + '@types/react-dom@19.2.3(@types/react@19.2.14)': dependencies: '@types/react': 19.2.14 @@ -3140,6 +3406,10 @@ snapshots: dependencies: csstype: 3.2.3 + '@types/xml2js@0.4.14': + dependencies: + '@types/node': 20.19.40 + '@typescript-eslint/eslint-plugin@8.59.2(@typescript-eslint/parser@8.59.2(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3))(eslint@9.39.4(jiti@2.7.0))(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -4115,6 +4385,13 @@ snapshots: dependencies: hermes-estree: 0.25.1 + httpx@2.3.3: + dependencies: + '@types/node': 20.19.40 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + ignore@5.3.2: {} ignore@7.0.5: {} @@ -4126,6 +4403,8 @@ snapshots: imurmurhash@0.1.4: {} + ini@1.3.5: {} + internal-slot@1.1.0: dependencies: es-errors: 1.3.0 @@ -4316,6 +4595,10 @@ snapshots: dependencies: json-buffer: 3.0.1 + kitx@2.2.0: + dependencies: + '@types/node': 22.19.19 + language-subtag-registry@0.3.20: {} language-tags@1.0.9: @@ -4386,6 +4669,8 @@ snapshots: lodash.merge@4.6.2: {} + lodash@4.18.1: {} + loose-envify@1.4.0: dependencies: js-tokens: 3.0.0 @@ -4429,6 +4714,12 @@ snapshots: minipass@7.1.3: {} + moment-timezone@0.5.45: + dependencies: + moment: 2.30.1 + + moment@2.30.1: {} + motion-dom@12.38.0: dependencies: motion-utils: 12.36.0 @@ -4728,6 +5019,8 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 + sax@1.6.0: {} + scheduler@0.27.0: {} semver@6.3.1: {} @@ -4824,6 +5117,8 @@ snapshots: signal-exit@4.1.0: {} + sm3@1.0.3: {} + source-map-js@1.2.1: {} stable-hash@0.0.5: {} @@ -5111,6 +5406,13 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.2.0 + xml2js@0.6.2: + dependencies: + sax: 1.6.0 + xmlbuilder: 11.0.1 + + xmlbuilder@11.0.1: {} + yallist@3.1.1: {} zod-validation-error@4.0.2(zod@4.4.3): diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index e4e2161..1616e5a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,4 +1,5 @@ allowBuilds: + '@alicloud/openapi-core': false '@prisma/client': true '@prisma/engines': true esbuild: true diff --git a/src/app/api/auth/send-otp/route.ts b/src/app/api/auth/send-otp/route.ts index b8b8143..8a8cff3 100644 --- a/src/app/api/auth/send-otp/route.ts +++ b/src/app/api/auth/send-otp/route.ts @@ -3,6 +3,7 @@ import { z } from "zod"; import { rateLimit } from "@/lib/rate-limit"; import { getClientIp } from "@/lib/current-user"; import { getRedis } from "@/lib/redis"; +import { sendOtpSms } from "@/lib/sms"; import { ok, ERR } from "@/lib/api-response"; const Body = z.object({ @@ -37,21 +38,40 @@ export async function POST(req: NextRequest) { // 生成 6 位验证码 const code = String(Math.floor(100000 + Math.random() * 900000)); - // 缓存到 Redis(5 分钟过期) + // 缓存到 Redis(5 分钟过期)。Redis 未配置时 dev 仍能通过万能码 123456 走完整流程 const redis = getRedis(); if (redis) { await redis.set(`sms:otp:${phone}`, code, "EX", 300); } - // TODO[团队]: 接入真实短信服务(阿里云 / 火山引擎 SMS) - // - 模板:${SMS_TEMPLATE_CODE} - // - 签名:${SMS_SIGN_NAME} - // - 参数:{ code, expireMin: 5 } - if (process.env.NODE_ENV !== "production") { - console.log(`[dev-otp] 发送给 ${phone}: ${code}(开发环境也接受万能码 123456)`); + // 调阿里云短信发送 + const sms = await sendOtpSms(phone, code); + + if (sms.ok) { + console.log(`[sms] sent phone=${phone} bizId=${sms.bizId}`); + return ok({ message: "验证码已发送", expiresIn: 300 }); } - return ok({ message: "验证码已发送", expiresIn: 300 }); + // 失败处理: + // - SMS 未配置且非生产 → 控制台打 code, 仍返回成功(开发态联调) + // - 阿里云明确返回参数 / 触发流控 → 422 + // - 其它 → 500 + if (sms.errorCode === "SMS_NOT_CONFIGURED") { + if (process.env.NODE_ENV !== "production") { + console.log(`[dev-otp] SMS 未配置, 验证码 ${phone}: ${code}(也可用万能码 123456)`); + return ok({ message: "验证码已发送", expiresIn: 300 }); + } + console.error("[sms] 生产环境短信未配置, 检查 SMS_* 环境变量"); + return ERR.INTERNAL("短信服务未配置"); + } + + console.error( + `[sms] 发送失败 phone=${phone} code=${sms.errorCode} message=${sms.errorMessage}`, + ); + if (sms.errorCode?.startsWith("isv.")) { + return ERR.VALIDATION(sms.errorMessage ?? "短信发送失败"); + } + return ERR.INTERNAL("短信发送失败"); } catch (e) { console.error("[POST /api/auth/send-otp]", e); return ERR.INTERNAL(); diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 3a72ae4..68774b2 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -1,6 +1,7 @@ import NextAuth, { type NextAuthConfig } from "next-auth"; import Credentials from "next-auth/providers/credentials"; import { prisma } from "./prisma"; +import { getRedis } from "./redis"; import { z } from "zod"; /** @@ -101,8 +102,10 @@ export const authConfig: NextAuthConfig = { export const { handlers, signIn, signOut, auth } = NextAuth(authConfig); /** - * 校验 OTP 验证码(开发态:固定接受 "123456")。 - * 生产态:从 Redis 读取并比对,校验后删除避免重放。 + * 校验 OTP 验证码。 + * - 开发态万能码 "123456" 始终通过(仅 NODE_ENV !== "production") + * - 否则从 Redis 读取并比对,校验通过后立即 del 避免重放 + * - Redis 未配置时:dev 接受任意 6 位(联调用),prod 直接拒绝 */ async function verifyOtp(phone: string, code: string): Promise { if (process.env.NODE_ENV !== "production" && code === "123456") { @@ -110,14 +113,19 @@ async function verifyOtp(phone: string, code: string): Promise { return true; } - // TODO[团队]: 接入 Redis - // const redis = getRedis(); - // if (!redis) return false; - // const key = `sms:otp:${phone}`; - // const stored = await redis.get(key); - // if (!stored || stored !== code) return false; - // await redis.del(key); - // return true; + const redis = getRedis(); + if (!redis) { + if (process.env.NODE_ENV !== "production") { + // dev 联调态:Redis 没配置时也能走完整流程 + return /^\d{6}$/.test(code); + } + console.error("[auth] 生产环境 Redis 未配置,OTP 校验直接拒绝"); + return false; + } - return false; + const key = `sms:otp:${phone}`; + const stored = await redis.get(key); + if (!stored || stored !== code) return false; + await redis.del(key); + return true; } diff --git a/src/lib/sms.ts b/src/lib/sms.ts new file mode 100644 index 0000000..eaafdd9 --- /dev/null +++ b/src/lib/sms.ts @@ -0,0 +1,83 @@ +/** + * 阿里云短信服务客户端 + * + * 接入: dysmsapi.aliyuncs.com (短信服务在国内是全局服务,所有调用走 cn-hangzhou) + * 文档: https://help.aliyun.com/document_detail/419298.html + * + * 环境变量: + * SMS_ACCESS_KEY RAM 子账号 AccessKey ID + * SMS_SECRET_KEY RAM 子账号 AccessKey Secret + * SMS_SIGN_NAME 已审核通过的签名 (例: 广州气元科技) + * SMS_TEMPLATE_CODE 已审核通过的模板 Code (例: SMS_506210397) + * + * 模板内容必须严格使用 ${code} 占位符,例如: + * "您的验证码是 ${code},5 分钟内有效,请勿泄露。" + */ +import Dysmsapi, * as $Dysmsapi from "@alicloud/dysmsapi20170525"; +import * as $OpenApi from "@alicloud/openapi-client"; +import * as $Util from "@alicloud/tea-util"; + +let client: Dysmsapi | null = null; + +function getClient(): Dysmsapi | null { + if (client) return client; + const accessKeyId = process.env.SMS_ACCESS_KEY; + const accessKeySecret = process.env.SMS_SECRET_KEY; + if (!accessKeyId || !accessKeySecret) return null; + const config = new $OpenApi.Config({ accessKeyId, accessKeySecret }); + config.endpoint = "dysmsapi.aliyuncs.com"; + client = new Dysmsapi(config); + return client; +} + +export interface SendOtpResult { + ok: boolean; + /** 阿里云返回的 BizId,排查问题时给阿里云工单用 */ + bizId?: string; + /** 阿里云错误码 / 失败原因 */ + errorCode?: string; + errorMessage?: string; +} + +/** + * 发送 6 位验证码短信。 + * 返回 ok=true 表示阿里云已受理 (用户收到短信通常在 30 秒内); + * ok=false 时 errorCode 给出原因 (如 isv.MOBILE_NUMBER_ILLEGAL / isv.BUSINESS_LIMIT_CONTROL 等)。 + * + * 若 SMS_ACCESS_KEY/SMS_SECRET_KEY 未配置,函数直接返回 ok=false 且 errorCode='SMS_NOT_CONFIGURED', + * 调用方可根据该信号 fallback 到 dev console.log 输出验证码。 + */ +export async function sendOtpSms( + phone: string, + code: string, +): Promise { + const c = getClient(); + const signName = process.env.SMS_SIGN_NAME; + const templateCode = process.env.SMS_TEMPLATE_CODE; + if (!c || !signName || !templateCode) { + return { ok: false, errorCode: "SMS_NOT_CONFIGURED" }; + } + + const req = new $Dysmsapi.SendSmsRequest({ + phoneNumbers: phone, + signName, + templateCode, + templateParam: JSON.stringify({ code }), + }); + + try { + const resp = await c.sendSmsWithOptions(req, new $Util.RuntimeOptions({})); + const body = resp.body; + if (body?.code === "OK") { + return { ok: true, bizId: body.bizId }; + } + return { + ok: false, + errorCode: body?.code ?? "UNKNOWN", + errorMessage: body?.message, + }; + } catch (e) { + const msg = e instanceof Error ? e.message : String(e); + return { ok: false, errorCode: "EXCEPTION", errorMessage: msg }; + } +}