├── .eslintignore ├── .eslintrc ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── question.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── publish.yml │ └── test.yml ├── .gitignore ├── .nvmrc ├── .prettierrc.js ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── config ├── tsconfig.base.json ├── tsconfig.sdk.cjs.json ├── tsconfig.sdk.esm.json └── tsconfig.test.json ├── jest.config.ts ├── package.json ├── scripts ├── build.sh ├── start.sh └── test.sh ├── src ├── core │ ├── sdk-exceptions.ts │ └── sdk.ts ├── index.ts ├── modules │ ├── base-module.ts │ ├── token │ │ └── index.ts │ ├── users │ │ └── index.ts │ └── utils │ │ ├── index.ts │ │ └── ownershipABIs.ts ├── types │ ├── didt-types.ts │ ├── exception-types.ts │ ├── index.ts │ ├── sdk-types.ts │ ├── utils-types.ts │ └── wallet-types.ts └── utils │ ├── codec.ts │ ├── ec-recover.ts │ ├── fetch.ts │ ├── issuer.ts │ ├── parse-didt.ts │ ├── rest.ts │ └── type-guards.ts ├── test ├── lib │ ├── constants.ts │ └── factories.ts ├── spec │ ├── core │ │ ├── sdk-exceptions │ │ │ ├── error-factories.spec.ts │ │ │ └── magic-admin-sdk-error │ │ │ │ └── constructor.spec.ts │ │ └── sdk │ │ │ └── constructor.spec.ts │ ├── index.spec.ts │ ├── modules │ │ ├── base-module │ │ │ └── constructor.spec.ts │ │ ├── token │ │ │ ├── decode.spec.ts │ │ │ ├── getIssuer.spec.ts │ │ │ ├── getPublicAddress.spec.ts │ │ │ └── validate.spec.ts │ │ ├── users │ │ │ ├── getMetadataByIssuer.spec.ts │ │ │ ├── getMetadataByPublicAddress.spec.ts │ │ │ ├── getMetadataByToken.spec.ts │ │ │ ├── logoutByIssuer.spec.ts │ │ │ ├── logoutByPublicAddress.spec.ts │ │ │ └── logoutByToken.spec.ts │ │ └── utils │ │ │ ├── parseAuthorizationHeader.spec.ts │ │ │ └── validateTokenOwnership.spec.ts │ └── utils │ │ ├── codec.spec.ts │ │ ├── issuer │ │ ├── generateIssuerFromPublicAddress.spec.ts │ │ └── parsePublicAddressFromIssuer.spec.ts │ │ ├── parse-didt.spec.ts │ │ ├── rest │ │ ├── emitRequest.spec.ts │ │ ├── get.spec.ts │ │ └── post.spec.ts │ │ └── type-guards │ │ └── isDIDTClaim.spec.ts └── tsconfig.json ├── tsconfig.json └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | jest.config.ts 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@ikscodes/eslint-config"], 3 | "parserOptions": { 4 | "project": [ 5 | "./tsconfig.json", 6 | "./test/tsconfig.json" 7 | ] 8 | }, 9 | "rules": { 10 | "import/extensions": 0, 11 | "no-alert": 0, 12 | "@typescript-eslint/await-thenable": 0, 13 | "react/button-has-type": 0, 14 | "no-cond-assign": 0, 15 | "class-methods-use-this": 0, 16 | "no-underscore-dangle": 0, 17 | "no-useless-constructor": 0, 18 | // Note: you must disable the base rule as it can report incorrect errors 19 | "no-shadow": 0, 20 | "@typescript-eslint/no-shadow": "warn", 21 | "no-empty-function": 0 22 | }, 23 | "settings": { 24 | "import/resolver": { 25 | "typescript": { 26 | "directory": [ 27 | "./tsconfig.json", 28 | "./test/tsconfig.json" 29 | ] 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Codeowners used to ensure Magic full-time employees review all PRs 2 | * @magiclabs/magic-engineering 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Use this template to report a bug. 4 | title: "[DESCRIPTIVE BUG NAME]" 5 | labels: 🐛 Bug Report, 🔍 Needs Triage 6 | --- 7 | 8 | ### ✅ Prerequisites 9 | 10 | - [ ] Did you perform a cursory search of open issues? Is this bug already reported elsewhere? 11 | - [ ] Are you running the latest SDK version? 12 | - [ ] Are you reporting to the correct repository (`@magic-sdk/admin`)? 13 | 14 | ### 🐛 Description 15 | 16 | [Description of the bug.] 17 | 18 | ### 🧩 Steps to Reproduce 19 | 20 | 1. [First Step] 21 | 2. [Second Step] 22 | 3. [and so on...] 23 | 24 | ### 🤔 Expected behavior 25 | 26 | [What you expected to happen?] 27 | 28 | ### 😮 Actual behavior 29 | 30 | [What actually happened? Please include any error stack traces you encounter.] 31 | 32 | ### 💻 Code Sample 33 | 34 | [If possible, please provide a code repository, gist, code snippet or sample files to reproduce the issue.] 35 | 36 | ### 🌎 Environment 37 | 38 | | Software | Version(s) | 39 | | ------------------ | ---------- | 40 | | `@magic-sdk/admin` | 41 | | Node | 42 | | `yarn` | 43 | | Operating System | 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Use this template to request a new feature. 4 | title: "[DESCRIPTIVE FEATURE NAME]" 5 | labels: ✨Feature Request 6 | --- 7 | 8 | ### ✅ Prerequisites 9 | 10 | - [ ] Did you perform a cursory search of open issues? Is this feature already requested elsewhere? 11 | - [ ] Are you reporting to the correct repository (`@magic-sdk/admin`)? 12 | 13 | ### ✨ Feature Request 14 | 15 | [Description of the feature.] 16 | 17 | ## 🧩 Context 18 | 19 | [Explain any additional context or rationale for this feature. What are you trying to accomplish?] 20 | 21 | ## 💻 Examples 22 | 23 | [Do you have any example(s) for the requested feature? If so, describe/demonstrate your example(s) here.] 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Use this template to request help or ask a question. 4 | title: "[WHAT'S YOUR QUESTION?]" 5 | labels: ❓Question 6 | --- 7 | 8 | ### ✅ Prerequisites 9 | 10 | - [ ] Did you perform a cursory search of open issues? Is this question already asked elsewhere? 11 | - [ ] Are you reporting to the correct repository (`@magic-sdk/admin`)? 12 | 13 | ### ❓ Question 14 | 15 | [Ask your question here, please be as detailed as possible!] 16 | 17 | ### 🌎 Environment 18 | 19 | | Software | Version(s) | 20 | | ------------------ | ---------- | 21 | | `@magic-sdk/admin` | 22 | | Node | 23 | | `yarn` | 24 | | Operating System | 25 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### 📦 Pull Request 2 | 3 | 4 | 5 | ### ✅ Fixed Issues 6 | 7 | 8 | 9 | ### 🚨 Test instructions 10 | 11 | 12 | 13 | ### ⚠️ Don't forget to add a [semver](https://semver.org/) label! 14 | 20 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | on: 3 | push: 4 | branches: 5 | - "master" 6 | 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-20.04 10 | name: Publish 11 | if: "!contains(github.event.head_commit.message, 'ci skip') && !contains(github.event.head_commit.message, 'skip ci')" 12 | steps: 13 | - uses: actions/checkout@v3 14 | with: 15 | token: ${{ secrets.ADMIN_TOKEN }} 16 | 17 | - name: Prepare repository 18 | run: git fetch --unshallow --tags 19 | 20 | - name: Setup node 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: 20 24 | cache: 'yarn' 25 | - run: | 26 | yarn -v 27 | yarn 28 | 29 | - name: Create Release 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.ADMIN_TOKEN }} 32 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 33 | run: | 34 | yarn 35 | yarn build 36 | yarn auto shipit 37 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | concurrency: 3 | group: tests-${{ github.ref }} 4 | cancel-in-progress: true 5 | on: 6 | push: 7 | branches: 8 | - "master" 9 | pull_request: 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-20.04 14 | name: Run style/security checks & tests 15 | container: 16 | image: node:16-slim 17 | steps: 18 | - uses: actions/checkout@v3 19 | 20 | - name: Setup node 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: 16 24 | cache: 'yarn' 25 | - run: | 26 | yarn -v 27 | yarn 28 | 29 | - name: Lint 30 | run: yarn run lint 31 | 32 | - name: Audit Prod Dependencies 33 | run: yarn audit --groups dependencies || true 34 | 35 | - name: Build 36 | run: yarn run build 37 | 38 | - name: Test 39 | run: yarn run test 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # Dependencies 4 | /node_modules 5 | /dist 6 | 7 | # Misc 8 | /.idea 9 | .DS_Store 10 | npm-debug.log* 11 | yarn-error.log* 12 | .DS_Store 13 | .vscode 14 | 15 | # Use Yarn! 16 | package-lock.json 17 | 18 | # Test artifacts 19 | /coverage 20 | /.nyc_output 21 | 22 | # ENV 23 | .env -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 20.10 2 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@ikscodes/prettier-config'); 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # v2.4.1 (Tue Apr 23 2024) 2 | 3 | #### 🐛 Bug Fix 4 | 5 | - feat: add username data field to metadata call response [#127](https://github.com/magiclabs/magic-admin-js/pull/127) ([@am-hernandez](https://github.com/am-hernandez)) 6 | 7 | #### Authors: 1 8 | 9 | - A.M. Hernandez ([@am-hernandez](https://github.com/am-hernandez)) 10 | 11 | --- 12 | 13 | # v2.4.0 (Thu Apr 04 2024) 14 | 15 | #### 🚀 Enhancement 16 | 17 | - Fixing ABIs [#126](https://github.com/magiclabs/magic-admin-js/pull/126) ([@bengriffin1](https://github.com/bengriffin1)) 18 | 19 | #### Authors: 1 20 | 21 | - Ben Griffin ([@bengriffin1](https://github.com/bengriffin1)) 22 | 23 | --- 24 | 25 | # v2.3.0 (Thu Apr 04 2024) 26 | 27 | #### 🚀 Enhancement 28 | 29 | - Updating yarn.lock [#125](https://github.com/magiclabs/magic-admin-js/pull/125) ([@bengriffin1](https://github.com/bengriffin1)) 30 | - Switching to use ethers and accept RPC url for token gating validation [#124](https://github.com/magiclabs/magic-admin-js/pull/124) ([@bengriffin1](https://github.com/bengriffin1) [@Ethella](https://github.com/Ethella)) 31 | 32 | #### Authors: 2 33 | 34 | - Ben Griffin ([@bengriffin1](https://github.com/bengriffin1)) 35 | - Jerry Liu ([@Ethella](https://github.com/Ethella)) 36 | 37 | --- 38 | 39 | # v2.2.0 (Wed Apr 03 2024) 40 | 41 | #### 🚀 Enhancement 42 | 43 | - Adding type and abis [#122](https://github.com/magiclabs/magic-admin-js/pull/122) ([@bengriffin1](https://github.com/bengriffin1) [@Ethella](https://github.com/Ethella)) 44 | 45 | #### 🐛 Bug Fix 46 | 47 | - Pdeexp 594 update admin sdk dependency versions to support web 3 [#123](https://github.com/magiclabs/magic-admin-js/pull/123) ([@Ethella](https://github.com/Ethella)) 48 | 49 | #### Authors: 2 50 | 51 | - Ben Griffin ([@bengriffin1](https://github.com/bengriffin1)) 52 | - Jerry Liu ([@Ethella](https://github.com/Ethella)) 53 | 54 | --- 55 | 56 | # v2.1.0 (Fri Jan 19 2024) 57 | 58 | #### 🚀 Enhancement 59 | 60 | - Fix ESM bundle generation and imports [#119](https://github.com/magiclabs/magic-admin-js/pull/119) ([@romin-halltari](https://github.com/romin-halltari)) 61 | 62 | #### Authors: 1 63 | 64 | - [@romin-halltari](https://github.com/romin-halltari) 65 | 66 | --- 67 | 68 | # v2.0.1 (Wed Oct 04 2023) 69 | 70 | #### 🐛 Bug Fix 71 | 72 | - update yarn.lock [#117](https://github.com/magiclabs/magic-admin-js/pull/117) ([@Ethella](https://github.com/Ethella)) 73 | - Remove dependencies of atob [#116](https://github.com/magiclabs/magic-admin-js/pull/116) ([@Ethella](https://github.com/Ethella)) 74 | 75 | #### Authors: 1 76 | 77 | - Jerry Liu ([@Ethella](https://github.com/Ethella)) 78 | 79 | --- 80 | 81 | # v2.0.0 (Mon Jul 10 2023) 82 | 83 | #### 💥 Breaking Change 84 | 85 | - Validate 'aud' in DID Token [#111](https://github.com/magiclabs/magic-admin-js/pull/111) ([@magic-ravi](https://github.com/magic-ravi)) 86 | 87 | #### Authors: 1 88 | 89 | - Ravi Bhankharia ([@magic-ravi](https://github.com/magic-ravi)) 90 | 91 | --- 92 | 93 | # v2.0.0 (July 10, 2023) 94 | 95 | ## Summary 96 | - 🚀 **Added:** Magic Connect developers can now use the Admin SDK to validate DID tokens. [#111](https://github.com/magiclabs/magic-admin-js/pull/111) ([@magic-ravi](https://github.com/magic-ravi)) 97 | - ⚠️ **Changed:** After creating the Magic instance, it is now necessary to call a new initialize method for Magic Connect developers that want to utilize the Admin SDK. [#111](https://github.com/magiclabs/magic-admin-js/pull/111) ([@magic-ravi](https://github.com/magic-ravi)) 98 | - 🛡️ **Security:** Additional validation of `aud` (client ID) is now being done during initialization of the SDK. [#111](https://github.com/magiclabs/magic-admin-js/pull/111) ([@magic-ravi](https://github.com/magic-ravi)) 99 | 100 | ## Developer Notes 101 | 102 | ### 🚀 Added 103 | 104 | #### Admin SDK for MC 105 | Magic Connect developers can now use the Admin SDK to validate DID tokens. 106 | 107 | **Details** 108 | There is full support for all `TokenResource` SDK methods for MC. This is intended to be used with client side `magic-js` SDK which will now emit an `id-token-created` event with a DID token upon login via the `connectWithUI` method. 109 | 110 | This functionality is replicated on our other SDKs on Python and Ruby. 111 | 112 | ### ⚠️ Changed 113 | 114 | #### Constructor initialization 115 | 116 | The existing constructor has been deprecated in place of a new async `init` method. 117 | The `init` method will pull clientId from Magic servers if one is not provided in the `options` parameter. 118 | 119 | **Previous Version** 120 | ```javascript 121 | const magic = new Magic(secretKey); 122 | try { 123 | magic.token.validate(DIDT); 124 | } catch (e) { 125 | console.log(e); 126 | } 127 | try { 128 | await magic.users.getMetadataByToken(DIDT); 129 | } catch (e) { 130 | console.log(e); 131 | } 132 | ``` 133 | 134 | **Current Version** 135 | ```javascript 136 | const magic = await Magic.init(mcSecretKey); 137 | try { 138 | magic.token.validate(DIDT); 139 | } catch (e) { 140 | console.log(e); 141 | } 142 | try { 143 | await magic.users.getMetadataByToken(DIDT); 144 | } catch (e) { 145 | console.log(e); 146 | } 147 | ``` 148 | 149 | #### Attachment Validation 150 | 151 | - Skip validation of attachment if 'none' is passed in `validate`. 152 | 153 | ### 🛡️ Security 154 | 155 | #### Client ID Validation 156 | 157 | Additional validation of `aud` (client ID) is now being done during initialization of the SDK. This is for both Magic Connect and Magic Auth developers. 158 | 159 | 160 | ### 🚨 Breaking 161 | 162 | None, all changes are fully backwards compatiable. 163 | 164 | ### Authors: 1 165 | 166 | - Ravi Bhankharia ([@magic-ravi](https://github.com/magic-ravi)) 167 | 168 | # v1.10.1 (Fri Jul 07 2023) 169 | 170 | #### 🐛 Bug Fix 171 | 172 | - * add file extension [#112](https://github.com/magiclabs/magic-admin-js/pull/112) ([@Ethella](https://github.com/Ethella)) 173 | 174 | #### Authors: 1 175 | 176 | - Jerry Liu ([@Ethella](https://github.com/Ethella)) 177 | 178 | --- 179 | 180 | # v1.10.0 (Wed May 03 2023) 181 | 182 | #### 🚀 Enhancement 183 | 184 | - [FIX]: removing unneeded NFT module and related code [#108](https://github.com/magiclabs/magic-admin-js/pull/108) ([@bengriffin1](https://github.com/bengriffin1)) 185 | 186 | #### Authors: 1 187 | 188 | - Ben Griffin ([@bengriffin1](https://github.com/bengriffin1)) 189 | 190 | --- 191 | 192 | # v1.9.2 (Fri Mar 24 2023) 193 | 194 | #### 🐛 Bug Fix 195 | 196 | - [FIX]: adding json header [#107](https://github.com/magiclabs/magic-admin-js/pull/107) ([@bengriffin1](https://github.com/bengriffin1)) 197 | 198 | #### Authors: 1 199 | 200 | - Ben Griffin ([@bengriffin1](https://github.com/bengriffin1)) 201 | 202 | --- 203 | 204 | # v1.9.1 (Fri Mar 24 2023) 205 | 206 | #### 🐛 Bug Fix 207 | 208 | - [FIX]: updating response from API server to fit new schema [#106](https://github.com/magiclabs/magic-admin-js/pull/106) ([@bengriffin1](https://github.com/bengriffin1)) 209 | 210 | #### Authors: 1 211 | 212 | - Ben Griffin ([@bengriffin1](https://github.com/bengriffin1)) 213 | 214 | --- 215 | 216 | # v1.9.1 (Wed Mar 22 2023) 217 | 218 | #### 🐛 Bug Fix 219 | 220 | - Update modules/mint/index.ts to account for new API response format [#106](https://github.com/magiclabs/magic-admin-js/pull/106) ([@bengriffin1](https://github.com/bengriffin1)) 221 | 222 | #### Authors: 1 223 | 224 | - Ben Griffin ([@bengriffin1](https://github.com/bengriffin1)) 225 | 226 | --- 227 | 228 | # v1.9.0 (Wed Mar 22 2023) 229 | 230 | #### 🚀 Enhancement 231 | 232 | - Adding mint module for minting NFTs [#105](https://github.com/magiclabs/magic-admin-js/pull/105) ([@bengriffin1](https://github.com/bengriffin1)) 233 | 234 | #### 🐛 Bug Fix 235 | 236 | - Update CHANGELOG.md [#104](https://github.com/magiclabs/magic-admin-js/pull/104) ([@justinnout](https://github.com/justinnout)) 237 | 238 | #### Authors: 2 239 | 240 | - Ben Griffin ([@bengriffin1](https://github.com/bengriffin1)) 241 | - Justin Herrera ([@justinnout](https://github.com/justinnout)) 242 | 243 | --- 244 | 245 | # v1.9.0 (Mon Mar 20 2023) 246 | 247 | #### 🚀 Enhancement 248 | 249 | - New module for NFT minting + delivery [#105](https://github.com/magiclabs/magic-admin-js/pull/105) ([@bengriffin1](https://github.com/bengriffin1)) 250 | 251 | #### Authors: 1 252 | 253 | - Ben Griffin ([@bengriffin1](https://github.com/bengriffin1)) 254 | 255 | --- 256 | # v1.8.0 (Fri Feb 17 2023) 257 | 258 | #### 🚀 Enhancement 259 | 260 | - Support new multi-chain wallet functionality [#100](https://github.com/magiclabs/magic-admin-js/pull/100) ([@justinnout](https://github.com/justinnout)) 261 | 262 | #### Authors: 1 263 | 264 | - Justin Herrera ([@justinnout](https://github.com/justinnout)) 265 | 266 | --- 267 | # v1.4.1 (Sat Apr 16 2022) 268 | 269 | #### 🐛 Bug Fix 270 | 271 | - Add shims for atob [#88](https://github.com/magiclabs/magic-admin-js/pull/88) ([@Ethella](https://github.com/Ethella)) 272 | 273 | #### Authors: 1 274 | 275 | - Jerry Liu ([@Ethella](https://github.com/Ethella)) 276 | 277 | --- 278 | 279 | # v1.4.0 (Tue Mar 29 2022) 280 | 281 | #### 🚀 Enhancement 282 | 283 | - feature: make package environment agnostic [#81](https://github.com/magiclabs/magic-admin-js/pull/81) ([@f5io](https://github.com/f5io)) 284 | 285 | #### ⚠️ Pushed to `master` 286 | 287 | - Update CircleCI to use Node 16 ([@smithki](https://github.com/smithki)) 288 | 289 | #### 🏠 Internal 290 | 291 | - Converted all Ava and Sinon testing to Jest [#83](https://github.com/magiclabs/magic-admin-js/pull/83) ([@mushfichowdhury-magic](https://github.com/mushfichowdhury-magic)) 292 | 293 | #### Authors: 3 294 | 295 | - [@mushfichowdhury-magic](https://github.com/mushfichowdhury-magic) 296 | - Ian K Smith ([@smithki](https://github.com/smithki)) 297 | - Joe Harlow ([@f5io](https://github.com/f5io)) 298 | 299 | --- 300 | 301 | # v1.3.4 (Thu Dec 23 2021) 302 | 303 | #### 🐛 Bug Fix 304 | 305 | - Fixed broken docs link in README [#72](https://github.com/magiclabs/magic-admin-js/pull/72) ([@lukecarr](https://github.com/lukecarr) [@smithki](https://github.com/smithki)) 306 | 307 | #### 🔩 Dependency Updates 308 | 309 | - Bump path-parse from 1.0.6 to 1.0.7 [#63](https://github.com/magiclabs/magic-admin-js/pull/63) ([@dependabot[bot]](https://github.com/dependabot[bot]) [@smithki](https://github.com/smithki)) 310 | - Bump glob-parent from 5.1.0 to 5.1.2 [#61](https://github.com/magiclabs/magic-admin-js/pull/61) ([@dependabot[bot]](https://github.com/dependabot[bot]) [@smithki](https://github.com/smithki)) 311 | - Bump normalize-url from 4.5.0 to 4.5.1 [#60](https://github.com/magiclabs/magic-admin-js/pull/60) ([@dependabot[bot]](https://github.com/dependabot[bot]) [@smithki](https://github.com/smithki)) 312 | - Bump hosted-git-info from 2.8.7 to 2.8.9 [#59](https://github.com/magiclabs/magic-admin-js/pull/59) ([@dependabot[bot]](https://github.com/dependabot[bot]) [@smithki](https://github.com/smithki)) 313 | - Bump lodash from 4.17.19 to 4.17.21 [#58](https://github.com/magiclabs/magic-admin-js/pull/58) ([@dependabot[bot]](https://github.com/dependabot[bot]) [@smithki](https://github.com/smithki)) 314 | - Bump gitlog from 4.0.3 to 4.0.4 [#57](https://github.com/magiclabs/magic-admin-js/pull/57) ([@dependabot[bot]](https://github.com/dependabot[bot]) [@smithki](https://github.com/smithki)) 315 | - Bump y18n from 4.0.0 to 4.0.1 [#56](https://github.com/magiclabs/magic-admin-js/pull/56) ([@dependabot[bot]](https://github.com/dependabot[bot]) [@smithki](https://github.com/smithki)) 316 | - Bump elliptic from 6.5.3 to 6.5.4 [#53](https://github.com/magiclabs/magic-admin-js/pull/53) ([@dependabot[bot]](https://github.com/dependabot[bot]) [@smithki](https://github.com/smithki)) 317 | - Bump ini from 1.3.5 to 1.3.8 [#51](https://github.com/magiclabs/magic-admin-js/pull/51) ([@dependabot[bot]](https://github.com/dependabot[bot]) [@smithki](https://github.com/smithki)) 318 | 319 | #### Authors: 3 320 | 321 | - [@dependabot[bot]](https://github.com/dependabot[bot]) 322 | - Ian K Smith ([@smithki](https://github.com/smithki)) 323 | - Luke Carr ([@lukecarr](https://github.com/lukecarr)) 324 | 325 | --- 326 | 327 | # v1.3.3 (Thu Dec 23 2021) 328 | 329 | #### 🐛 Bug Fix 330 | 331 | - getMetadata calls now return phone number [#74](https://github.com/magiclabs/magic-admin-js/pull/74) ([@hcote](https://github.com/hcote)) 332 | 333 | #### Authors: 1 334 | 335 | - Hunter Cote ([@hcote](https://github.com/hcote)) 336 | 337 | --- 338 | 339 | # v1.3.2 (Fri Nov 12 2021) 340 | 341 | #### 🐛 Bug Fix 342 | 343 | - allow getMetadata calls to return social provider [#71](https://github.com/magiclabs/magic-admin-js/pull/71) ([@hcote](https://github.com/hcote)) 344 | 345 | #### Authors: 1 346 | 347 | - Hunter Cote ([@hcote](https://github.com/hcote)) 348 | 349 | --- 350 | 351 | # v1.3.1 (Tue Oct 05 2021) 352 | 353 | #### 🐛 Bug Fix 354 | 355 | - fix: prevent URI encoding [#69](https://github.com/magiclabs/magic-admin-js/pull/69) ([@chiefGui](https://github.com/chiefGui)) 356 | 357 | #### 🏠 Internal 358 | 359 | - Cleanup internal scripts [#47](https://github.com/magiclabs/magic-admin-js/pull/47) ([@smithki](https://github.com/smithki)) 360 | 361 | #### Authors: 2 362 | 363 | - Guilherme Oderdenge ([@chiefGui](https://github.com/chiefGui)) 364 | - Ian K Smith ([@smithki](https://github.com/smithki)) 365 | 366 | --- 367 | 368 | # v1.3.0 (Tue Nov 17 2020) 369 | 370 | #### 🚀 Enhancement 371 | 372 | - Add continuous delivery via 'auto' package [#46](https://github.com/magiclabs/magic-admin-js/pull/46) ([@smithki](https://github.com/smithki)) 373 | 374 | #### 🐛 Bug Fix 375 | 376 | - Update validate function to support attachment feature [#15](https://github.com/magiclabs/magic-admin-js/pull/15) ([@smithki](https://github.com/smithki)) 377 | - Fix misspelling of 'argumenet' [#43](https://github.com/magiclabs/magic-admin-js/pull/43) ([@brianrlewis](https://github.com/brianrlewis)) 378 | - Add 'UtilsModule' & 'parseAuthorizationHeader' helper [#39](https://github.com/magiclabs/magic-admin-js/pull/39) ([@smithki](https://github.com/smithki)) 379 | - Remove vulnerability badge [#38](https://github.com/magiclabs/magic-admin-js/pull/38) ([@FYJen](https://github.com/FYJen)) 380 | - Add vulnerabilities and circleci badges [#35](https://github.com/magiclabs/magic-admin-js/pull/35) ([@FYJen](https://github.com/FYJen)) 381 | - Add issue templates and contributing guide [#26](https://github.com/magiclabs/magic-admin-js/pull/26) ([@smithki](https://github.com/smithki)) 382 | - Encode query URI components and implement logout v2 [#22](https://github.com/magiclabs/magic-admin-js/pull/22) ([@smithki](https://github.com/smithki)) 383 | - Add 'getUserBy*' methods and generalize REST API services [#18](https://github.com/magiclabs/magic-admin-js/pull/18) ([@smithki](https://github.com/smithki)) 384 | - Validate `nbf` field [#40](https://github.com/magiclabs/magic-admin-js/pull/40) ([@smithki](https://github.com/smithki)) 385 | - fix name of logout request body [#14](https://github.com/magiclabs/magic-admin-js/pull/14) ([@Dizigen](https://github.com/Dizigen)) 386 | - Add logoutByIssuer method [#13](https://github.com/magiclabs/magic-admin-js/pull/13) ([@smithki](https://github.com/smithki)) 387 | - Remove middlewares [#12](https://github.com/magiclabs/magic-admin-js/pull/12) ([@smithki](https://github.com/smithki)) 388 | - Add new header + fix decode type signature [#11](https://github.com/magiclabs/magic-admin-js/pull/11) ([@smithki](https://github.com/smithki)) 389 | - Remove default exports [#10](https://github.com/magiclabs/magic-admin-js/pull/10) ([@smithki](https://github.com/smithki)) 390 | - Add CircleCI config [#8](https://github.com/magiclabs/magic-admin-js/pull/8) ([@smithki](https://github.com/smithki)) 391 | - Add unit tests [#7](https://github.com/magiclabs/magic-admin-js/pull/7) ([@smithki](https://github.com/smithki)) 392 | 393 | #### ⚠️ Pushed to `master` 394 | 395 | - v0.1.0-beta.8 ([@smithki](https://github.com/smithki)) 396 | - 1.2.2 ([@smithki](https://github.com/smithki)) 397 | - Rename variable for clarity ([@smithki](https://github.com/smithki)) 398 | - Fix logical error related to 'nbf' check ([@smithki](https://github.com/smithki)) 399 | - 1.2.1 ([@smithki](https://github.com/smithki)) 400 | - Add 'files' field to 'package.json' ([@smithki](https://github.com/smithki)) 401 | - 1.2.0 ([@smithki](https://github.com/smithki)) 402 | - Fix broken CircleCI badge ([@smithki](https://github.com/smithki)) 403 | - 1.1.0 ([@smithki](https://github.com/smithki)) 404 | - Update CONTRIBUTING.md ([@smithki](https://github.com/smithki)) 405 | - Update README.md ([@smithki](https://github.com/smithki)) 406 | - Update issue templates ([@smithki](https://github.com/smithki)) 407 | - v1.0.0 ([@smithki](https://github.com/smithki)) 408 | - v0.1.0-beta.10 ([@smithki](https://github.com/smithki)) 409 | - v0.1.0-beta.9 ([@smithki](https://github.com/smithki)) 410 | - Update CHANGELOG ([@smithki](https://github.com/smithki)) 411 | - v0.1.0-beta.7 ([@smithki](https://github.com/smithki)) 412 | - Remove console statement ([@smithki](https://github.com/smithki)) 413 | - Fix bug in 'isDIDTClaim' ([@smithki](https://github.com/smithki)) 414 | - v0.1.0-beta.6 ([@smithki](https://github.com/smithki)) 415 | - 0.1.0-beta.5 ([@Dizigen](https://github.com/Dizigen)) 416 | - Update TypeScript to 3.8.3 ([@smithki](https://github.com/smithki)) 417 | - Remove node E2E ([@smithki](https://github.com/smithki)) 418 | - v0.1.0-beta.4 ([@smithki](https://github.com/smithki)) 419 | - Update README ([@smithki](https://github.com/smithki)) 420 | - Fix test ([@smithki](https://github.com/smithki)) 421 | - v0.1.0-beta.3 ([@smithki](https://github.com/smithki)) 422 | - v0.1.0-beta.2 ([@smithki](https://github.com/smithki)) 423 | - v0.1.0-beta.0 ([@smithki](https://github.com/smithki)) 424 | - Add boilerplate code ([@smithki](https://github.com/smithki)) 425 | - Initial commit ([@FYJen](https://github.com/FYJen)) 426 | 427 | #### 🔩 Dependency Updates 428 | 429 | - Bump lodash from 4.17.15 to 4.17.19 [#41](https://github.com/magiclabs/magic-admin-js/pull/41) ([@dependabot[bot]](https://github.com/dependabot[bot])) 430 | - Bump elliptic from 6.5.2 to 6.5.3 [#42](https://github.com/magiclabs/magic-admin-js/pull/42) ([@dependabot[bot]](https://github.com/dependabot[bot])) 431 | - Bump node-fetch from 2.6.0 to 2.6.1 [#45](https://github.com/magiclabs/magic-admin-js/pull/45) ([@dependabot[bot]](https://github.com/dependabot[bot])) 432 | - Bump acorn from 7.1.0 to 7.1.1 [#36](https://github.com/magiclabs/magic-admin-js/pull/36) ([@dependabot[bot]](https://github.com/dependabot[bot]) [@smithki](https://github.com/smithki)) 433 | 434 | #### Authors: 5 435 | 436 | - [@dependabot[bot]](https://github.com/dependabot[bot]) 437 | - Arthur Jen ([@FYJen](https://github.com/FYJen)) 438 | - Brian Lewis ([@brianrlewis](https://github.com/brianrlewis)) 439 | - David He ([@Dizigen](https://github.com/Dizigen)) 440 | - Ian K Smith ([@smithki](https://github.com/smithki)) 441 | 442 | --- 443 | 444 | ## Upcoming Changes 445 | 446 | #### Fixed 447 | 448 | - ... 449 | 450 | #### Changed 451 | 452 | - ... 453 | 454 | #### Added 455 | 456 | - ... 457 | 458 | ## `1.2.2` - 06/11/2020 459 | 460 | #### Fixed 461 | 462 | - Fixed a logical error that would incorrectly parse the `nbf` field. 463 | 464 | ## `1.2.1` - 06/11/2020 465 | 466 | #### Changed 467 | 468 | - Added `"files"` field to `package.json` to make the NPM package less cumbersome. 469 | 470 | ## `1.2.0` - 06/11/2020 471 | 472 | #### Added 473 | 474 | - Enforce the `nbf` field for DID Tokens parsed via `TokenModule.validate`. 475 | 476 | ## `1.1.0` - 05/25/2020 477 | 478 | #### Added 479 | 480 | - Introduced `UtilsModule` along with a `parseAuthorizationHeader` helper method. 481 | 482 | ## `1.0.0` - 04/09/2020 483 | 484 | This is the first release our changelog records. Future updates will be logged in the following format: 485 | 486 | #### Fixed 487 | 488 | - Bug fixes and patches will be described here. 489 | 490 | #### Changed 491 | 492 | - Changes (breaking or otherwise) to current APIs will be described here. 493 | 494 | #### Added 495 | 496 | - New features or APIs will be described here. 497 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via an **issue**. This can be a feature request or a bug report. After a maintainer has triaged your issue, you are welcome to collaborate on a pull request. If your change is small or uncomplicated, you are welcome to open an issue and pull request simultaneously. 4 | 5 | Please note we have a **code of conduct**, please follow it in all your interactions with the project. 6 | 7 | ## Setting up for Local Development 8 | 9 | 1. Fork this repostiory. 10 | 2. Clone your fork. 11 | 3. Create a new branch in your local repository with the following pattern: 12 | 13 | - For bug fixes: `bug/#[issue_number]/[descriptive_bug_name]` 14 | - For features: `feature/#[issue_number]/[descriptive_feature_name]` 15 | - For chores/the rest: `chore/[descriptive_chore_name]` 16 | 17 | 4. Install dependencies with Yarn: `yarn install` 18 | 5. Start building for development: `yarn start` 19 | 20 | ## Opening a Pull Request 21 | 22 | 1. Open a pull request from your fork/branch to the upstream `master` branch of _this_ repository. 23 | 2. Add a label for the [semver](https://semver.org/) update corresponding to your changes: `patch`, `minor`, or `major`. 24 | 3. A maintainer will review your code changes and offer feedback or suggestions if necessary. Once your changes are approved, a maintainer will merge the pull request for you and publish a release. 25 | 26 | ## Cutting a release 27 | 28 | We use [`auto`](https://github.com/intuit/auto) as our continous delivery tool. Cutting a release is just a matter of merging to `master`. For pre-releases, you can create a `next` branch as the base for your experimental/W.I.P. feature. Please familiarize yourself with the [documentation for `auto`](https://intuit.github.io/auto/docs) if you are in a position to cut a release. 29 | 30 | ## Contributor Covenant Code of Conduct 31 | 32 | ### Our Pledge 33 | 34 | We as members, contributors, and leaders pledge to make participation in our 35 | community a harassment-free experience for everyone, regardless of age, body 36 | size, visible or invisible disability, ethnicity, sex characteristics, gender 37 | identity and expression, level of experience, education, socio-economic status, 38 | nationality, personal appearance, race, religion, or sexual identity 39 | and orientation. 40 | 41 | We pledge to act and interact in ways that contribute to an open, welcoming, 42 | diverse, inclusive, and healthy community. 43 | 44 | ### Our Standards 45 | 46 | Examples of behavior that contributes to a positive environment for our 47 | community include: 48 | 49 | - Demonstrating empathy and kindness toward other people 50 | - Being respectful of differing opinions, viewpoints, and experiences 51 | - Giving and gracefully accepting constructive feedback 52 | - Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience 53 | - Focusing on what is best not just for us as individuals, but for the overall community 54 | 55 | Examples of unacceptable behavior include: 56 | 57 | - The use of sexualized language or imagery, and sexual attention or advances of any kind 58 | - Trolling, insulting or derogatory comments, and personal or political attacks 59 | - Public or private harassment 60 | - Publishing others' private information, such as a physical or email address, without their explicit permission 61 | - Other conduct which could reasonably be considered inappropriate in a professional setting 62 | 63 | ### Enforcement Responsibilities 64 | 65 | Community leaders are responsible for clarifying and enforcing our standards of 66 | acceptable behavior and will take appropriate and fair corrective action in 67 | response to any behavior that they deem inappropriate, threatening, offensive, 68 | or harmful. 69 | 70 | Community leaders have the right and responsibility to remove, edit, or reject 71 | comments, commits, code, wiki edits, issues, and other contributions that are 72 | not aligned to this Code of Conduct, and will communicate reasons for moderation 73 | decisions when appropriate. 74 | 75 | ### Scope 76 | 77 | This Code of Conduct applies within all community spaces, and also applies when 78 | an individual is officially representing the community in public spaces. 79 | Examples of representing our community include using an official e-mail address, 80 | posting via an official social media account, or acting as an appointed 81 | representative at an online or offline event. 82 | 83 | ### Enforcement 84 | 85 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 86 | reported to the community leaders responsible for enforcement at [support@magic.link](mailto:support@magic.link). 87 | All complaints will be reviewed and investigated promptly and fairly. 88 | 89 | All community leaders are obligated to respect the privacy and security of the 90 | reporter of any incident. 91 | 92 | ### Enforcement Guidelines 93 | 94 | Community leaders will follow these Community Impact Guidelines in determining 95 | the consequences for any action they deem in violation of this Code of Conduct: 96 | 97 | #### 1. Correction 98 | 99 | **Community Impact**: Use of inappropriate language or other behavior deemed 100 | unprofessional or unwelcome in the community. 101 | 102 | **Consequence**: A private, written warning from community leaders, providing 103 | clarity around the nature of the violation and an explanation of why the 104 | behavior was inappropriate. A public apology may be requested. 105 | 106 | #### 2. Warning 107 | 108 | **Community Impact**: A violation through a single incident or series 109 | of actions. 110 | 111 | **Consequence**: A warning with consequences for continued behavior. No 112 | interaction with the people involved, including unsolicited interaction with 113 | those enforcing the Code of Conduct, for a specified period of time. This 114 | includes avoiding interactions in community spaces as well as external channels 115 | like social media. Violating these terms may lead to a temporary or 116 | permanent ban. 117 | 118 | #### 3. Temporary Ban 119 | 120 | **Community Impact**: A serious violation of community standards, including 121 | sustained inappropriate behavior. 122 | 123 | **Consequence**: A temporary ban from any sort of interaction or public 124 | communication with the community for a specified period of time. No public or 125 | private interaction with the people involved, including unsolicited interaction 126 | with those enforcing the Code of Conduct, is allowed during this period. 127 | Violating these terms may lead to a permanent ban. 128 | 129 | #### 4. Permanent Ban 130 | 131 | **Community Impact**: Demonstrating a pattern of violation of community 132 | standards, including sustained inappropriate behavior, harassment of an 133 | individual, or aggression toward or disparagement of classes of individuals. 134 | 135 | **Consequence**: A permanent ban from any sort of public interaction within 136 | the community. 137 | 138 | ### Attribution 139 | 140 | This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), 141 | version 2.0, available at 142 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 143 | 144 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 145 | enforcement ladder](https://github.com/mozilla/diversity). 146 | 147 | For answers to common questions about this code of conduct, see the FAQ at 148 | https://www.contributor-covenant.org/faq. Translations are available at 149 | https://www.contributor-covenant.org/translations. 150 | 151 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Fortmatic Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Magic Authentication Admin Javascript SDK 2 | 3 | [![Publish](https://github.com/magiclabs/magic-admin-js/actions/workflows/publish.yml/badge.svg?branch=master)](https://github.com/magiclabs/magic-admin-js/actions/workflows/publish.yml) 4 | > The Magic Admin SDK lets developers secure endpoints, manage users, and create middlewares via easy-to-use utilities. 5 | 6 |

7 | License · 8 | Changelog · 9 | Contributing Guide 10 |

11 | 12 | ## 📖 Documentation 13 | 14 | See the [developer documentation](https://magic.link/docs/api-reference/server-side-sdks/node) to learn how you can master the Magic Admin SDK in a matter of minutes. 15 | 16 | ## 🔗 Installation 17 | 18 | Integrating your Node.js application with Magic will require our server-side NPM package: 19 | 20 | ```bash 21 | # Via NPM: 22 | npm install --save @magic-sdk/admin 23 | 24 | # Via Yarn: 25 | yarn add @magic-sdk/admin 26 | ``` 27 | 28 | ## ⚡️ Quick Start 29 | 30 | Sign up or log in to the [developer dashboard](https://dashboard.magic.link) to receive API keys that will allow your application to interact with Magic's administration APIs. 31 | 32 | ```ts 33 | const { Magic } = require('@magic-sdk/admin'); 34 | 35 | // In async function: 36 | const magic = await Magic.init('YOUR_SECRET_API_KEY'); 37 | // OR 38 | Magic.init('YOUR_SECRET_API_KEY').then((magic) => { 39 | magic 40 | }); 41 | // Validate a token 42 | try { 43 | magic.token.validate("DIDToken"); 44 | } catch (e) { 45 | console.log(e); 46 | } 47 | // Magic Auth - Get User Email 48 | try { 49 | await magic.users.getMetadataByToken("DIDToken"); 50 | } catch (e) { 51 | console.log(e); 52 | } 53 | ``` 54 | -------------------------------------------------------------------------------- /config/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["es2018", "dom"], 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "target": "es6", 7 | "strict": true, 8 | "allowSyntheticDefaultImports": true, 9 | "experimentalDecorators": true, 10 | "noImplicitReturns": true, 11 | "noImplicitThis": true, 12 | "esModuleInterop": true, 13 | "downlevelIteration": true, 14 | "resolveJsonModule": true, 15 | "allowJs": true, 16 | "sourceMap": true, 17 | "declaration": true, 18 | }, 19 | "include": ["../src/**/*.ts"], 20 | "exclude": ["../node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /config/tsconfig.sdk.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/cjs", 5 | }, 6 | "include": ["../src/**/*.ts"] 7 | } 8 | -------------------------------------------------------------------------------- /config/tsconfig.sdk.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "lib": ["es2020", "dom"], 5 | "target": "es2020", 6 | "module": "es2020", 7 | "outDir": "../dist/esm" 8 | }, 9 | "include": ["../src/**/*.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /config/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "target": "es6", 6 | "strict": false, 7 | "noImplicitAny": false, 8 | "downlevelIteration": true, 9 | "esModuleInterop": true 10 | }, 11 | "include": ["../test/**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from '@jest/types'; 2 | 3 | const config: Config.InitialOptions = { 4 | maxWorkers: 2, 5 | preset: 'ts-jest', 6 | coverageReporters: ['text-summary', 'html'], 7 | collectCoverageFrom: ['./src/**/*.{ts,tsx,}'], 8 | collectCoverage: true, 9 | testTimeout: 30000, // 30s 10 | coverageThreshold: { 11 | global: { 12 | lines: 99, 13 | statements: 99, 14 | functions: 99, 15 | branches: 99, 16 | } 17 | }, 18 | globals: { 19 | 'ts-jest': { 20 | tsconfig: './test/tsconfig.json', 21 | isolatedModules: true, 22 | }, 23 | }, 24 | }; 25 | 26 | export default config; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@magic-sdk/admin", 3 | "version": "2.4.1", 4 | "description": "Magic Authentication Admin Javascript SDK.", 5 | "author": "Magic Labs ", 6 | "license": "MIT", 7 | "repository": "magiclabs/magic-admin-js", 8 | "main": "dist/cjs/index.js", 9 | "module": "dist/esm/index.js", 10 | "types": "dist/cjs/index.d.ts", 11 | "exports": { 12 | ".": { 13 | "import": "./dist/esm/index.js", 14 | "require": "./dist/cjs/index.js" 15 | } 16 | }, 17 | "files": [ 18 | "dist/**/*" 19 | ], 20 | "scripts": { 21 | "start": "npm run clean:build && ./scripts/start.sh", 22 | "build": "npm run clean:build && ./scripts/build.sh", 23 | "build-esm": "esbuild src/index.ts --bundle --outdir=dist/esm --platform=node --target=esnext", 24 | "test": "npm run clean:test-artifacts && ./scripts/test.sh", 25 | "lint": "eslint --fix src/**/*.ts", 26 | "clean": "npm-run-all -s clean:*", 27 | "clean:test-artifacts": "rimraf coverage", 28 | "clean:build": "rimraf dist", 29 | "clean_node_modules": "rimraf node_modules", 30 | "prepare": "husky install" 31 | }, 32 | "devDependencies": { 33 | "@ikscodes/eslint-config": "^8.4.1", 34 | "@istanbuljs/nyc-config-typescript": "^1.0.1", 35 | "@types/jest": "^27.4.1", 36 | "@types/node": "^13.1.2", 37 | "@types/node-fetch": "^2.5.4", 38 | "@typescript-eslint/eslint-plugin": "^6.21.0", 39 | "auto": "11.0.5", 40 | "boxen-cli": "^1.0.0", 41 | "esbuild": "^0.14.54", 42 | "eslint": "^8.56.0", 43 | "eslint-import-resolver-typescript": "^2.0.0", 44 | "eslint-plugin-import": "^2.29.1", 45 | "eslint-plugin-jsx-a11y": "^6.8.0", 46 | "eslint-plugin-prettier": "^5.1.3", 47 | "husky": "^8.0.3", 48 | "jest": "^27.5.1", 49 | "lint-staged": "^10.0.8", 50 | "npm-run-all": "~4.1.5", 51 | "prettier": "^3.2.4", 52 | "rimraf": "~3.0.0", 53 | "ts-jest": "^27.1.3", 54 | "ts-node": "^10.2.0", 55 | "tslint": "~5.20.1", 56 | "typescript": "^5.3.3" 57 | }, 58 | "dependencies": { 59 | "ethereum-cryptography": "^1.0.1", 60 | "ethers": "^6.11.1", 61 | "node-fetch": "^2.6.7" 62 | }, 63 | "lint-staged": { 64 | "*.{ts,tsx}": "eslint --fix" 65 | }, 66 | "auto": { 67 | "plugins": [ 68 | "npm", 69 | "released" 70 | ] 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo 4 | boxen --border-color cyan --dim-border --padding 1 "🏗 Building Magic Admin SDK for production." 5 | echo 6 | 7 | # Increase memory limit for Node 8 | export NODE_OPTIONS=--max_old_space_size=4096 9 | 10 | export NODE_ENV=production 11 | 12 | # Generate type declarations 13 | npx tsc -p ./config/tsconfig.sdk.esm.json --declaration --emitDeclarationOnly 14 | 15 | # Build ESM bundle 16 | npm run build-esm 17 | 18 | #Build CJS bundle 19 | npx tsc -p ./config/tsconfig.sdk.cjs.json 20 | -------------------------------------------------------------------------------- /scripts/start.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo 4 | boxen --border-color cyan --dim-border --padding 1 "🏗 Building Magic Admin SDK for development." 5 | echo 6 | 7 | # Increase memory limit for Node 8 | export NODE_OPTIONS=--max_old_space_size=4096 9 | 10 | export NODE_ENV=development 11 | 12 | npx tsc -w -p ./config/tsconfig.sdk.json 13 | -------------------------------------------------------------------------------- /scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo 4 | boxen --border-color cyan --dim-border --padding 1 "🚦 Running unit tests..." 5 | echo 6 | 7 | # Increase memory limit for Node 8 | export NODE_OPTIONS=--max_old_space_size=4096 9 | 10 | export NODE_ENV=test 11 | 12 | export TS_NODE_PROJECT="test/tsconfig.json" 13 | 14 | jest $input 15 | -------------------------------------------------------------------------------- /src/core/sdk-exceptions.ts: -------------------------------------------------------------------------------- 1 | import { ErrorCode } from '../types'; 2 | 3 | // --- Base SDK error class 4 | 5 | export class MagicAdminSDKError extends Error { 6 | __proto__ = Error; 7 | 8 | constructor( 9 | public code: ErrorCode, 10 | message: string, 11 | public data: any[] = [], 12 | ) { 13 | super(`Magic Admin SDK Error: [${code}] ${message}`); 14 | Object.setPrototypeOf(this, MagicAdminSDKError.prototype); 15 | } 16 | } 17 | 18 | // --- SDK error factories 19 | 20 | export function createTokenExpiredError() { 21 | return new MagicAdminSDKError(ErrorCode.TokenExpired, 'DID Token has expired. Request failed authentication.'); 22 | } 23 | 24 | export function createTokenCannotBeUsedYetError() { 25 | return new MagicAdminSDKError( 26 | ErrorCode.TokenCannotBeUsedYet, 27 | 'Given DID Token cannot be used at this time. Please check the `nbf` field and regenerate a new token with a suitable value.', 28 | ); 29 | } 30 | 31 | export function createIncorrectSignerAddressError() { 32 | return new MagicAdminSDKError( 33 | ErrorCode.IncorrectSignerAddress, 34 | 'Incorrect signer address for DID Token. Request failed authentication.', 35 | ); 36 | } 37 | 38 | export function createFailedRecoveringProofError() { 39 | return new MagicAdminSDKError( 40 | ErrorCode.FailedRecoveryProof, 41 | 'Failed to recover proof. Request failed authentication.', 42 | ); 43 | } 44 | 45 | export function createApiKeyMissingError() { 46 | return new MagicAdminSDKError( 47 | ErrorCode.ApiKeyMissing, 48 | 'Please provide a secret Magic API key that you acquired from the developer dashboard.', 49 | ); 50 | } 51 | 52 | export function createMalformedTokenError() { 53 | return new MagicAdminSDKError(ErrorCode.MalformedTokenError, 'The DID token is malformed or failed to parse.'); 54 | } 55 | 56 | export function createServiceError(...nestedErrors: any[]) { 57 | return new MagicAdminSDKError( 58 | ErrorCode.ServiceError, 59 | 'A service error occurred while communicating with the Magic API. Check the `data` key of this error object to see nested errors with additional context.', 60 | nestedErrors, 61 | ); 62 | } 63 | 64 | export function createExpectedBearerStringError() { 65 | return new MagicAdminSDKError( 66 | ErrorCode.ExpectedBearerString, 67 | 'Expected argument to be a string in the `Bearer {token}` format.', 68 | ); 69 | } 70 | 71 | export function createAudienceMismatchError() { 72 | return new MagicAdminSDKError( 73 | ErrorCode.AudienceMismatch, 74 | 'Audience does not match client ID. Please ensure your secret key matches the application which generated the DID token.', 75 | ); 76 | } 77 | -------------------------------------------------------------------------------- /src/core/sdk.ts: -------------------------------------------------------------------------------- 1 | import { createApiKeyMissingError } from './sdk-exceptions'; 2 | import { TokenModule } from '../modules/token'; 3 | import { UsersModule } from '../modules/users'; 4 | import { UtilsModule } from '../modules/utils'; 5 | import { MagicAdminSDKAdditionalConfiguration } from '../types'; 6 | import { get } from '../utils/rest'; 7 | 8 | export class MagicAdminSDK { 9 | public readonly apiBaseUrl: string; 10 | 11 | /** 12 | * Contains utilities for interacting with Decentralized Identity Tokens 13 | * (DIDTs). 14 | */ 15 | public readonly token: TokenModule; 16 | 17 | /** 18 | * Contains utilities for interacting with your Magic Authentication user 19 | * model. 20 | */ 21 | public readonly users: UsersModule; 22 | 23 | /** 24 | * Contains general utilities for Magic Admin SDK. 25 | */ 26 | public readonly utils: UtilsModule; 27 | 28 | /** 29 | * Unique client identifier 30 | */ 31 | public clientId: string | null; 32 | 33 | /** 34 | * Deprecated. Use `init` instead. 35 | * @param secretApiKey 36 | * @param options 37 | */ 38 | constructor( 39 | public readonly secretApiKey?: string, 40 | options?: MagicAdminSDKAdditionalConfiguration, 41 | ) { 42 | const endpoint = options?.endpoint ?? 'https://api.magic.link'; 43 | this.apiBaseUrl = endpoint.replace(/\/+$/, ''); 44 | this.clientId = options?.clientId ?? null; 45 | // Assign API Modules 46 | this.token = new TokenModule(this); 47 | this.users = new UsersModule(this); 48 | this.utils = new UtilsModule(this); 49 | } 50 | 51 | public static async init(secretApiKey?: string, options?: MagicAdminSDKAdditionalConfiguration) { 52 | if (!secretApiKey) throw createApiKeyMissingError(); 53 | 54 | let hydratedOptions = options ?? {}; 55 | 56 | const endpoint = hydratedOptions.endpoint ?? 'https://api.magic.link'; 57 | const apiBaseUrl = endpoint.replace(/\/+$/, ''); 58 | 59 | if (!hydratedOptions.clientId) { 60 | const resp = await get<{ 61 | client_id: string | null; 62 | app_scope: string | null; 63 | }>(`${apiBaseUrl}/v1/admin/client/get`, secretApiKey); 64 | hydratedOptions = { ...hydratedOptions, clientId: resp.client_id }; 65 | } 66 | 67 | return new MagicAdminSDK(secretApiKey, hydratedOptions); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { MagicAdminSDK as Magic } from './core/sdk'; 2 | export { MagicAdminSDKError as SDKError } from './core/sdk-exceptions'; 3 | export * from './types'; 4 | -------------------------------------------------------------------------------- /src/modules/base-module.ts: -------------------------------------------------------------------------------- 1 | import { MagicAdminSDK } from '../core/sdk'; 2 | 3 | export abstract class BaseModule { 4 | constructor(protected readonly sdk: MagicAdminSDK) {} 5 | } 6 | -------------------------------------------------------------------------------- /src/modules/token/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable prefer-destructuring */ 2 | import { BaseModule } from '../base-module'; 3 | import { ParsedDIDToken } from '../../types'; 4 | import { 5 | createFailedRecoveringProofError, 6 | createIncorrectSignerAddressError, 7 | createTokenExpiredError, 8 | createMalformedTokenError, 9 | createTokenCannotBeUsedYetError, 10 | createAudienceMismatchError, 11 | } from '../../core/sdk-exceptions'; 12 | import { ecRecover } from '../../utils/ec-recover'; 13 | import { parseDIDToken } from '../../utils/parse-didt'; 14 | import { parsePublicAddressFromIssuer } from '../../utils/issuer'; 15 | 16 | export class TokenModule extends BaseModule { 17 | public validate(DIDToken: string, attachment = 'none') { 18 | let tokenSigner = ''; 19 | let attachmentSigner: string | null = null; 20 | let claimedIssuer = ''; 21 | let parsedClaim; 22 | let proof: string; 23 | let claim: string; 24 | 25 | try { 26 | const tokenParseResult = parseDIDToken(DIDToken); 27 | [proof, claim] = tokenParseResult.raw; 28 | parsedClaim = tokenParseResult.withParsedClaim[1]; 29 | claimedIssuer = parsePublicAddressFromIssuer(parsedClaim.iss); 30 | } catch { 31 | throw createMalformedTokenError(); 32 | } 33 | 34 | try { 35 | // Recover the token signer 36 | tokenSigner = ecRecover(claim, proof).toLowerCase(); 37 | 38 | // Recover the attachment signer 39 | if (attachment && attachment !== 'none') { 40 | attachmentSigner = ecRecover(attachment, parsedClaim.add).toLowerCase(); 41 | } 42 | } catch { 43 | throw createFailedRecoveringProofError(); 44 | } 45 | 46 | // Assert the expected signer 47 | if (claimedIssuer !== tokenSigner || (attachmentSigner && claimedIssuer !== attachmentSigner)) { 48 | throw createIncorrectSignerAddressError(); 49 | } 50 | 51 | const timeSecs = Math.floor(Date.now() / 1000); 52 | const nbfLeeway = 300; // 5 min grace period 53 | 54 | // Assert the token is not expired 55 | if (parsedClaim.ext < timeSecs) { 56 | throw createTokenExpiredError(); 57 | } 58 | 59 | // Assert the token is not used before allowed. 60 | if (parsedClaim.nbf - nbfLeeway > timeSecs) { 61 | throw createTokenCannotBeUsedYetError(); 62 | } 63 | 64 | // Assert the audience matches the client ID. 65 | if (this.sdk.clientId && parsedClaim.aud !== this.sdk.clientId) { 66 | throw createAudienceMismatchError(); 67 | } 68 | } 69 | 70 | public decode(DIDToken: string): ParsedDIDToken { 71 | const parsedToken = parseDIDToken(DIDToken); 72 | return parsedToken.withParsedClaim; 73 | } 74 | 75 | public getPublicAddress(DIDToken: string): string { 76 | const claim = this.decode(DIDToken)[1]; 77 | const claimedIssuer = claim.iss.split(':')[2]; 78 | return claimedIssuer; 79 | } 80 | 81 | public getIssuer(DIDToken: string): string { 82 | return this.decode(DIDToken)[1].iss; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/modules/users/index.ts: -------------------------------------------------------------------------------- 1 | import { BaseModule } from '../base-module'; 2 | import { createApiKeyMissingError } from '../../core/sdk-exceptions'; 3 | import { post, get } from '../../utils/rest'; 4 | import { generateIssuerFromPublicAddress } from '../../utils/issuer'; 5 | import { MagicUserMetadata, MagicWallet, WalletType } from '../../types'; 6 | 7 | export class UsersModule extends BaseModule { 8 | // --- User logout endpoints 9 | 10 | public async logoutByIssuer(issuer: string): Promise { 11 | if (!this.sdk.secretApiKey) throw createApiKeyMissingError(); 12 | const body = { issuer }; 13 | await post(`${this.sdk.apiBaseUrl}/v2/admin/auth/user/logout`, this.sdk.secretApiKey, body); 14 | } 15 | 16 | public async logoutByPublicAddress(publicAddress: string): Promise { 17 | const issuer = generateIssuerFromPublicAddress(publicAddress); 18 | await this.logoutByIssuer(issuer); 19 | } 20 | 21 | public async logoutByToken(DIDToken: string): Promise { 22 | const issuer = this.sdk.token.getIssuer(DIDToken); 23 | await this.logoutByIssuer(issuer); 24 | } 25 | 26 | // --- User metadata endpoints 27 | 28 | public async getMetadataByIssuer(issuer: string): Promise { 29 | return this.getMetadataByIssuerAndWallet(issuer, WalletType.NONE); 30 | } 31 | 32 | public async getMetadataByToken(DIDToken: string): Promise { 33 | const issuer = this.sdk.token.getIssuer(DIDToken); 34 | return this.getMetadataByIssuer(issuer); 35 | } 36 | 37 | public async getMetadataByPublicAddress(publicAddress: string): Promise { 38 | const issuer = generateIssuerFromPublicAddress(publicAddress); 39 | return this.getMetadataByIssuer(issuer); 40 | } 41 | 42 | public async getMetadataByTokenAndWallet(DIDToken: string, walletType: WalletType): Promise { 43 | const issuer = this.sdk.token.getIssuer(DIDToken); 44 | return this.getMetadataByIssuerAndWallet(issuer, walletType); 45 | } 46 | 47 | public async getMetadataByPublicAddressAndWallet( 48 | publicAddress: string, 49 | walletType: WalletType, 50 | ): Promise { 51 | const issuer = generateIssuerFromPublicAddress(publicAddress); 52 | return this.getMetadataByIssuerAndWallet(issuer, walletType); 53 | } 54 | 55 | public async getMetadataByIssuerAndWallet(issuer: string, walletType: WalletType): Promise { 56 | if (!this.sdk.secretApiKey) throw createApiKeyMissingError(); 57 | 58 | const data = await get<{ 59 | issuer: string | null; 60 | public_address: string | null; 61 | email: string | null; 62 | oauth_provider: string | null; 63 | phone_number: string | null; 64 | username: string | null; 65 | wallets: MagicWallet[] | null; 66 | }>(`${this.sdk.apiBaseUrl}/v1/admin/auth/user/get`, this.sdk.secretApiKey, { issuer, wallet_type: walletType }); 67 | 68 | return { 69 | issuer: data.issuer ?? null, 70 | publicAddress: data.public_address ?? null, 71 | email: data.email ?? null, 72 | oauthProvider: data.oauth_provider ?? null, 73 | phoneNumber: data.phone_number ?? null, 74 | username: data.username ?? null, 75 | wallets: data.wallets ?? null, 76 | }; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/modules/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "ethers"; 2 | import { BaseModule } from '../base-module'; 3 | import {createExpectedBearerStringError} from '../../core/sdk-exceptions'; 4 | import { ValidateTokenOwnershipResponse } from '../../types'; 5 | import { ERC1155ContractABI, ERC721ContractABI } from './ownershipABIs'; 6 | import { ErrorCode } from '../../types'; 7 | 8 | export class UtilsModule extends BaseModule { 9 | /** 10 | * Parse a raw DID Token from the given Authorization header. 11 | */ 12 | public parseAuthorizationHeader(header: string) { 13 | if (!header.toLowerCase().startsWith('bearer ')) { 14 | throw createExpectedBearerStringError(); 15 | } 16 | 17 | return header.substring(7); 18 | } 19 | 20 | // Token Gating function validates user ownership of wallet + NFT 21 | public async validateTokenOwnership( 22 | didToken: string, 23 | contractAddress: string, 24 | contractType: 'ERC721' | 'ERC1155', 25 | rpcURL: string, 26 | tokenId?: string, 27 | ): Promise { 28 | // Make sure if ERC1155 has a tokenId 29 | if (contractType === 'ERC1155' && !tokenId) { 30 | throw new Error('ERC1155 requires a tokenId'); 31 | } 32 | // Validate DID token 33 | let walletAddress; 34 | try { 35 | await this.sdk.token.validate(didToken); 36 | walletAddress = this.sdk.token.getPublicAddress(didToken); 37 | } catch (e: any) { 38 | // Check if code is malformed token 39 | if (e.code && e.code === 'ERROR_MALFORMED_TOKEN') { 40 | return { 41 | valid: false, 42 | error_code: 'UNAUTHORIZED', 43 | message: 'Invalid DID token: ' + ErrorCode.MalformedTokenError, 44 | }; 45 | } 46 | if (e.code === ErrorCode.TokenExpired) { 47 | return { 48 | valid: false, 49 | error_code: 'UNAUTHORIZED', 50 | message: 'Invalid DID token: ' + ErrorCode.TokenExpired, 51 | }; 52 | } 53 | throw new Error(e); 54 | } 55 | 56 | 57 | // Check on-chain if user owns NFT by calling contract with web3 58 | let balance = BigInt(0); 59 | const provider = new ethers.JsonRpcProvider(rpcURL); 60 | if (contractType === 'ERC721') { 61 | const contract = new ethers.Contract(contractAddress, ERC721ContractABI, provider); 62 | balance = BigInt(await contract.balanceOf(walletAddress)); 63 | } else { 64 | const contract = new ethers.Contract(contractAddress, ERC1155ContractABI, provider); 65 | balance = BigInt(await contract.balanceOf(walletAddress, tokenId)); 66 | } 67 | if (balance > BigInt(0)) { 68 | return { 69 | valid: true, 70 | error_code: '', 71 | message: '', 72 | }; 73 | } 74 | return { 75 | valid: false, 76 | error_code: 'NO_OWNERSHIP', 77 | message: 'User does not own this token.', 78 | }; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/modules/utils/ownershipABIs.ts: -------------------------------------------------------------------------------- 1 | export const ERC721ContractABI = [ 2 | "function balanceOf(address owner) view returns (uint balance)", 3 | ] 4 | export const ERC1155ContractABI = [ 5 | "function balanceOf(address owner, uint id) view returns (uint balance)", 6 | ] 7 | -------------------------------------------------------------------------------- /src/types/didt-types.ts: -------------------------------------------------------------------------------- 1 | /** The shape of metadata encoded within a DID Token. */ 2 | export interface Claim { 3 | iat: number; // Issued At Timestamp 4 | ext: number; // Expiration Timestamp 5 | iss: string; // Issuer of DID Token 6 | sub: string; // Subject 7 | aud: string; // Audience 8 | nbf: number; // Not Before Timestamp 9 | tid: string; // DID Token ID 10 | add: string; // Encrypted signature of arbitrary data 11 | } 12 | 13 | export type ParsedDIDToken = [string, Claim]; 14 | -------------------------------------------------------------------------------- /src/types/exception-types.ts: -------------------------------------------------------------------------------- 1 | export enum ErrorCode { 2 | MissingAuthHeader = 'ERROR_MISSING_AUTH_HEADER', 3 | TokenExpired = 'ERROR_DIDT_EXPIRED', 4 | TokenCannotBeUsedYet = 'ERROR_DIDT_CANNOT_BE_USED_YET', 5 | IncorrectSignerAddress = 'ERROR_INCORRECT_SIGNER_ADDR', 6 | FailedRecoveryProof = 'ERROR_FAILED_RECOVERING_PROOF', 7 | ApiKeyMissing = 'ERROR_SECRET_API_KEY_MISSING', 8 | MalformedTokenError = 'ERROR_MALFORMED_TOKEN', 9 | ServiceError = 'SERVICE_ERROR', 10 | ExpectedBearerString = 'EXPECTED_BEARER_STRING', 11 | AudienceMismatch = 'ERROR_AUDIENCE_MISMATCH', 12 | } 13 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './didt-types'; 2 | export * from './exception-types'; 3 | export * from './sdk-types'; 4 | export * from './wallet-types'; 5 | export * from './utils-types'; 6 | -------------------------------------------------------------------------------- /src/types/sdk-types.ts: -------------------------------------------------------------------------------- 1 | export interface MagicAdminSDKAdditionalConfiguration { 2 | endpoint?: string; 3 | clientId?: string | null; 4 | } 5 | 6 | export interface MagicWallet { 7 | network: string | null; 8 | publicAddress: string | null; 9 | walletType: string | null; 10 | } 11 | 12 | export interface MagicUserMetadata { 13 | issuer: string | null; 14 | publicAddress: string | null; 15 | email: string | null; 16 | oauthProvider: string | null; 17 | phoneNumber: string | null; 18 | username: string | null; 19 | wallets: MagicWallet[] | null; 20 | } 21 | -------------------------------------------------------------------------------- /src/types/utils-types.ts: -------------------------------------------------------------------------------- 1 | export interface ValidateTokenOwnershipResponse { 2 | valid: boolean; 3 | error_code: string; 4 | message: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/types/wallet-types.ts: -------------------------------------------------------------------------------- 1 | export enum WalletType { 2 | ETH = 'ETH', 3 | HARMONY = 'HARMONY', 4 | ICON = 'ICON', 5 | FLOW = 'FLOW', 6 | TEZOS = 'TEZOS', 7 | ZILLIQA = 'ZILLIQA', 8 | POLKADOT = 'POLKADOT', 9 | SOLANA = 'SOLANA', 10 | AVAX = 'AVAX', 11 | ALGOD = 'ALGOD', 12 | COSMOS = 'COSMOS', 13 | CELO = 'CELO', 14 | BITCOIN = 'BITCOIN', 15 | NEAR = 'NEAR', 16 | HELIUM = 'HELIUM', 17 | CONFLUX = 'CONFLUX', 18 | TERRA = 'TERRA', 19 | TAQUITO = 'TAQUITO', 20 | ED = 'ED', 21 | HEDERA = 'HEDERA', 22 | NONE = 'NONE', 23 | ANY = 'ANY', 24 | } 25 | -------------------------------------------------------------------------------- /src/utils/codec.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Check if global object window is defined 3 | */ 4 | export function isBrowser() { 5 | return typeof window !== 'undefined'; 6 | } 7 | 8 | /* 9 | * Decode base64 value, returns string 10 | * @Params: string 11 | */ 12 | export function decodeValue(value: string): string { 13 | if (!value) { 14 | return ''; 15 | } 16 | 17 | const valueToString = value.toString(); 18 | 19 | if (isBrowser()) { 20 | return atob(valueToString); 21 | } 22 | 23 | const buff = Buffer.from(valueToString, 'base64'); 24 | return buff.toString('ascii'); 25 | } 26 | -------------------------------------------------------------------------------- /src/utils/ec-recover.ts: -------------------------------------------------------------------------------- 1 | import { keccak256 } from 'ethereum-cryptography/keccak.js'; 2 | import { ecdsaRecover, publicKeyConvert } from 'ethereum-cryptography/secp256k1-compat.js'; 3 | import { utf8ToBytes, bytesToHex, hexToBytes } from 'ethereum-cryptography/utils.js'; 4 | 5 | function hashPersonalMessage(message: Uint8Array): Uint8Array { 6 | const prefix = utf8ToBytes(`\u0019Ethereum Signed Message:\n${message.length}`); 7 | const totalLength = prefix.length + message.length; 8 | 9 | const output = new Uint8Array(totalLength); 10 | output.set(prefix); 11 | output.set(message, prefix.length); 12 | 13 | return keccak256(output); 14 | } 15 | 16 | function getRecoveryBit(signature: Uint8Array): number { 17 | const bit = signature[64]; 18 | return bit - 27; 19 | } 20 | 21 | function prepareSignature(signature: string): string { 22 | return signature.slice(2); // strip the `0x` prefix 23 | } 24 | 25 | function publicKeyToAddress(publicKey: Uint8Array): string { 26 | const address = keccak256(publicKey.slice(1)).slice(-20); 27 | return `0x${bytesToHex(address)}`; 28 | } 29 | 30 | /** 31 | * Recover the signer from an Elliptic Curve signature. 32 | */ 33 | export function ecRecover(data: string, signature: string) { 34 | // Use ecdsaRecover on the Proof, to validate if it recovers to the expected 35 | // Claim, and expected Signer Address. 36 | 37 | const msg = utf8ToBytes(data); 38 | const sig = hexToBytes(prepareSignature(signature)); 39 | const recovery = getRecoveryBit(sig); 40 | const hash = hashPersonalMessage(msg); 41 | 42 | const publicKey = ecdsaRecover(sig.slice(0, 64), recovery, hash, false); 43 | const assertPublicKey = publicKeyConvert(publicKey, false); 44 | 45 | return publicKeyToAddress(assertPublicKey); 46 | } 47 | -------------------------------------------------------------------------------- /src/utils/fetch.ts: -------------------------------------------------------------------------------- 1 | import { RequestInit, Response } from 'node-fetch'; 2 | 3 | type Fetch = (url: string, init?: RequestInit) => Promise; 4 | 5 | /* istanbul ignore next */ 6 | export const fetch: Fetch = !(globalThis as any).fetch 7 | ? (url, init) => import('node-fetch').then(({ default: f }) => f(url, init)) 8 | : (globalThis as any).fetch; 9 | -------------------------------------------------------------------------------- /src/utils/issuer.ts: -------------------------------------------------------------------------------- 1 | export function generateIssuerFromPublicAddress(publicAddress: string, method = 'ethr') { 2 | return `did:${method}:${publicAddress}`; 3 | } 4 | 5 | export function parsePublicAddressFromIssuer(issuer: string) { 6 | return issuer.split(':')[2]?.toLowerCase() ?? ''; 7 | } 8 | -------------------------------------------------------------------------------- /src/utils/parse-didt.ts: -------------------------------------------------------------------------------- 1 | import { decodeValue } from './codec'; 2 | import { isDIDTClaim } from './type-guards'; 3 | import { createMalformedTokenError } from '../core/sdk-exceptions'; 4 | import { Claim, ParsedDIDToken } from '../types'; 5 | 6 | interface ParseDIDTokenResult { 7 | raw: [string, string]; 8 | withParsedClaim: ParsedDIDToken; 9 | } 10 | 11 | /** 12 | * Parses a DID Token so that the encoded `claim` is in object form. 13 | */ 14 | export function parseDIDToken(didToken: string): ParseDIDTokenResult { 15 | try { 16 | const [proof, claim] = JSON.parse(decodeValue(didToken)) as [string, string]; 17 | const parsedClaim = JSON.parse(claim) as Claim; 18 | if (isDIDTClaim(parsedClaim)) return { raw: [proof, claim], withParsedClaim: [proof, parsedClaim] }; 19 | throw new Error(); 20 | } catch { 21 | throw createMalformedTokenError(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/utils/rest.ts: -------------------------------------------------------------------------------- 1 | import { RequestInit } from 'node-fetch'; 2 | 3 | import { fetch } from './fetch'; 4 | import { createServiceError } from '../core/sdk-exceptions'; 5 | 6 | interface MagicAPIResponse { 7 | data?: TData; 8 | error_code?: string; 9 | message?: string; 10 | status?: string | number; 11 | } 12 | 13 | /** 14 | * Performs a `fetch` to the given URL with the configured `init` object. 15 | */ 16 | async function emitRequest(url: string, init?: RequestInit): Promise> { 17 | const json: MagicAPIResponse = await fetch(url, init) 18 | .then((res) => res.json()) 19 | .catch((err) => { 20 | throw createServiceError(err); 21 | }); 22 | 23 | if (json.status !== 'ok') { 24 | throw createServiceError(json); 25 | } 26 | 27 | return json.data ?? {}; 28 | } 29 | 30 | /** 31 | * Generates an encoded URL with query string from a dictionary of values. 32 | */ 33 | function generateQuery>(url: string, params?: T) { 34 | let query = '?'; 35 | if (params) { 36 | for (const [key, value] of Object.entries(params)) query += `${key}=${value}&`; 37 | query = query.slice(0, -1); // Remove trailing "&" 38 | } 39 | return params ? `${url}${query}` : url; 40 | } 41 | 42 | /** 43 | * POSTs to Magic's API. 44 | */ 45 | export function post, TResponse>( 46 | url: string, 47 | secretApiKey: string, 48 | body: TBody, 49 | ) { 50 | return emitRequest(url, { 51 | method: 'POST', 52 | headers: { 'X-Magic-Secret-key': secretApiKey }, 53 | body: JSON.stringify(body), 54 | }); 55 | } 56 | 57 | /** 58 | * GETs from Magic's API. 59 | */ 60 | export function get(url: string, secretApiKey: string, params?: any) { 61 | const urlWithParams = generateQuery(url, params); 62 | return emitRequest(urlWithParams, { 63 | method: 'GET', 64 | headers: { 'X-Magic-Secret-key': secretApiKey }, 65 | }); 66 | } 67 | -------------------------------------------------------------------------------- /src/utils/type-guards.ts: -------------------------------------------------------------------------------- 1 | /* 2 | This file contains our type guards. 3 | 4 | Type guards are a feature of TypeScript which narrow the type signature of 5 | intesection types (types that can be one thing or another). 6 | 7 | @see 8 | https://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-differentiating-types 9 | */ 10 | 11 | import { Claim } from '../types'; 12 | 13 | /** Assert `value` is `undefined`. */ 14 | function isUndefined(value: any): value is undefined { 15 | return typeof value === 'undefined'; 16 | } 17 | 18 | /** Assert `value` is `null`. */ 19 | function isNull(value: any): value is null { 20 | return value === null; 21 | } 22 | 23 | /** Assert `value` is `null` or `undefined`. */ 24 | function isNil(value: any): value is null | undefined { 25 | return isNull(value) || isUndefined(value); 26 | } 27 | 28 | /** Assert `value` contains all required DID Token members. */ 29 | export function isDIDTClaim(value: any): value is Claim { 30 | return ( 31 | !isNil(value) && 32 | !isNil(value.iat) && 33 | !isNil(value.ext) && 34 | !isNil(value.iss) && 35 | !isNil(value.sub) && 36 | !isNil(value.aud) && 37 | !isNil(value.nbf) && 38 | !isNil(value.tid) && 39 | !isNil(value.add) 40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /test/lib/constants.ts: -------------------------------------------------------------------------------- 1 | export const API_FULL_URL = 'https://api.magic.link'; 2 | export const API_KEY = 'sk_test_123'; 3 | 4 | export const VALID_DIDT = 5 | 'WyIweGUwMjQzNTVlNDI5ZGNhZDM1MTdhZDk5ZWEzNDEwYWJmZDQ1YjBiNjM5OGIwNjY1NGRiYTQxNzljODdlMTYyNzgxNTc1YjA5ODFjNjU4ZjcwMjYwZTQ5MjMwZGE5NDg4YTA0ZDk5NzBlYjM4ZTZmZGRlY2Q2NTA5YTAyN2IwOGI5MWIiLCJ7XCJpYXRcIjoxNTg1MDExMjA0LFwiZXh0XCI6MTkwMDQxMTIwNCxcImlzc1wiOlwiZGlkOmV0aHI6MHhCMmVjOWI2MTY5OTc2MjQ5MWI2NTQyMjc4RTlkRkVDOTA1MGY4MDg5XCIsXCJzdWJcIjpcIjZ0RlhUZlJ4eWt3TUtPT2pTTWJkUHJFTXJwVWwzbTNqOERReWNGcU8ydHc9XCIsXCJhdWRcIjpcImRpZDptYWdpYzpmNTQxNjhlOS05Y2U5LTQ3ZjItODFjOC03Y2IyYTk2YjI2YmFcIixcIm5iZlwiOjE1ODUwMTEyMDQsXCJ0aWRcIjpcIjJkZGY1OTgzLTk4M2ItNDg3ZC1iNDY0LWJjNWUyODNhMDNjNVwiLFwiYWRkXCI6XCIweDkxZmJlNzRiZTZjNmJmZDhkZGRkZDkzMDExYjA1OWI5MjUzZjEwNzg1NjQ5NzM4YmEyMTdlNTFlMGUzZGYxMzgxZDIwZjUyMWEzNjQxZjIzZWI5OWNjYjM0ZTNiYzVkOTYzMzJmZGViYzhlZmE1MGNkYjQxNWU0NTUwMDk1MmNkMWNcIn0iXQ=='; 6 | 7 | export const DECODED_DIDT = `["0xe024355e429dcad3517ad99ea3410abfd45b0b6398b06654dba4179c87e162781575b0981c658f70260e49230da9488a04d9970eb38e6fddecd6509a027b08b91b","{\\"iat\\":1585011204,\\"ext\\":1900411204,\\"iss\\":\\"did:ethr:0xB2ec9b61699762491b6542278E9dFEC9050f8089\\",\\"sub\\":\\"6tFXTfRxykwMKOOjSMbdPrEMrpUl3m3j8DQycFqO2tw=\\",\\"aud\\":\\"did:magic:f54168e9-9ce9-47f2-81c8-7cb2a96b26ba\\",\\"nbf\\":1585011204,\\"tid\\":\\"2ddf5983-983b-487d-b464-bc5e283a03c5\\",\\"add\\":\\"0x91fbe74be6c6bfd8ddddd93011b059b9253f10785649738ba217e51e0e3df1381d20f521a3641f23eb99ccb34e3bc5d96332fdebc8efa50cdb415e45500952cd1c\\"}"]`; 8 | export const VALID_DIDT_WITH_INVALID_RECOVERY_BIT = 9 | 'WyIweGUwMjQzNTVlNDI5ZGNhZDM1MTdhZDk5ZWEzNDEwYWJmZDQ1YjBiNjM5OGIwNjY1NGRiYTQxNzljODdlMTYyNzgxNTc1YjA5ODFjNjU4ZjcwMjYwZTQ5MjMwZGE5NDg4YTA0ZDk5NzBlYjM4ZTZmZGRlY2Q2NTA5YTAyN2IwOGI5MjMiLCJ7XCJpYXRcIjoxNTg1MDExMjA0LFwiZXh0XCI6MTkwMDQxMTIwNCxcImlzc1wiOlwiZGlkOmV0aHI6MHhCMmVjOWI2MTY5OTc2MjQ5MWI2NTQyMjc4RTlkRkVDOTA1MGY4MDg5XCIsXCJzdWJcIjpcIjZ0RlhUZlJ4eWt3TUtPT2pTTWJkUHJFTXJwVWwzbTNqOERReWNGcU8ydHc9XCIsXCJhdWRcIjpcImRpZDptYWdpYzpmNTQxNjhlOS05Y2U5LTQ3ZjItODFjOC03Y2IyYTk2YjI2YmFcIixcIm5iZlwiOjE1ODUwMTEyMDQsXCJ0aWRcIjpcIjJkZGY1OTgzLTk4M2ItNDg3ZC1iNDY0LWJjNWUyODNhMDNjNVwiLFwiYWRkXCI6XCIweDkxZmJlNzRiZTZjNmJmZDhkZGRkZDkzMDExYjA1OWI5MjUzZjEwNzg1NjQ5NzM4YmEyMTdlNTFlMGUzZGYxMzgxZDIwZjUyMWEzNjQxZjIzZWI5OWNjYjM0ZTNiYzVkOTYzMzJmZGViYzhlZmE1MGNkYjQxNWU0NTUwMDk1MmNkMWNcIn0iXQ=='; 10 | 11 | export const VALID_DIDT_PARSED_CLAIMS = { 12 | iat: 1585011204, 13 | ext: 1900411204, 14 | iss: 'did:ethr:0xB2ec9b61699762491b6542278E9dFEC9050f8089', 15 | sub: '6tFXTfRxykwMKOOjSMbdPrEMrpUl3m3j8DQycFqO2tw=', 16 | aud: 'did:magic:f54168e9-9ce9-47f2-81c8-7cb2a96b26ba', 17 | nbf: 1585011204, 18 | tid: '2ddf5983-983b-487d-b464-bc5e283a03c5', 19 | add: 20 | '0x91fbe74be6c6bfd8ddddd93011b059b9253f10785649738ba217e51e0e3df1381d20f521a3641f23eb99ccb34e3bc5d96332fdebc8efa50cdb415e45500952cd1c', 21 | }; 22 | 23 | export const VALID_DIDT_DECODED = [ 24 | '0xe024355e429dcad3517ad99ea3410abfd45b0b6398b06654dba4179c87e162781575b0981c658f70260e49230da9488a04d9970eb38e6fddecd6509a027b08b91b', 25 | VALID_DIDT_PARSED_CLAIMS, 26 | ]; 27 | 28 | export const VALID_FUTURE_MARKED_DIDT = 29 | 'WyIweDkzZjRiNTViYzRlN2E1ZWJkZTdmMzVkYzczMWE5NWFmOGYwZjVlMWQyMWQ5ZDYwZWQxM2Y4YmYzMmNiN2UwOTQ1MDM0MGI1Y2IyNTIxODZkNWQ3OTFiOTAyODZhYmY1NzM3YzMxN2M5NzNhMmQzMGY0MWZmYmFlNGU0NTdmMjE4MWIiLCJ7XCJpYXRcIjoxNTkxOTE0NTgyLFwiZXh0XCI6MjIyMjcxNDU4MixcImlzc1wiOlwiZGlkOmV0aHI6MHg0YzMzMmQ5QzRhMmEwNjY1YzNmODg1MTU1YjlFOTFmZEIzMDBlRTc2XCIsXCJzdWJcIjpcIms4NUtaR09Ycl9vMTYxNGdFVGN6Yzlac0phTjV4cjF2TVFXSWhnbjQ1Slk9XCIsXCJhdWRcIjpcImRpZDptYWdpYzoyMWI4ZjRkZS02ZmIzLTQ0M2YtOGM0MC04ODcwODJjNDQ1MjNcIixcIm5iZlwiOjE5MDczMTQ1ODIsXCJ0aWRcIjpcIjVhMjhjMjQwLWRmYzYtNDg2Ni04ODk1LTVkYzBhOTVkNWJkN1wiLFwiYWRkXCI6XCIweGRlMmI1ODgyNjUyZGExOTY4YWNlZTIyYWUyNGI2OWYxNThlZjg1NDQzOGE0OTlmMThjZGZlZDU3MzEwOGIxNzExYjQ2OWQ3MzQ5NzdhNGQ4NGJlM2RiODc2OTBkZjFmZjk4MTVjN2Y3NDIxNjIxMGY4Y2JhMGJmYzQ2ZGIwYjhkMWNcIn0iXQ=='; 30 | 31 | export const INVALID_DIDT_MALFORMED_CLAIM = 32 | 'WyIweDRiN2Y0ODgzNTczOGM2ZjViZmI5MTIyYmEzN2EwN2EyZDYwMTUwZjM2NGNhMDIwZjUyYWVlZjc3MmNlODk2ZWExYmJlYzc3OGZlMmFhNWNjODc3ZmVmOWQyOTJmN2U2MWZhMGZjY2M2NWIwZWZlZGVkMThmNzI0MzZkNjQ4MzdjMWMiLCJ7XCJ0aGlzSXNXUk9OR1wiOlwiSEVMTE8gV09STERcIn0iXQ=='; 33 | 34 | export const MALFORMED_DIDT_PARSED_CLAIMS = { 35 | thisIsWRONG: 'HELLO WORLD', 36 | }; 37 | 38 | export const INVALID_DIDT_MALFORMED_CLAIM_DECODED = [ 39 | '0x4b7f48835738c6f5bfb9122ba37a07a2d60150f364ca020f52aeef772ce896ea1bbec778fe2aa5cc877fef9d292f7e61fa0fccc65b0efeded18f72436d64837c1c', 40 | MALFORMED_DIDT_PARSED_CLAIMS, 41 | ]; 42 | 43 | export const INVALID_SIGNER_DIDT = 44 | 'WyIweDBhNTk4NmE1NDdiMzNhMDAxODIxNmRiNjk0YzNiMDg3YTU3MTk1Nzg4ZTZmMDc2NDg4NzA2ZTQ3ZmFhNjFhYzMzZDczZTM4ZmM5ZDA0YzU2YWVmZWNiMTAxMDA4OGEwNmFlOWFiZTE5ZDIyYWQ4MzNiMDhhM2VlNWNmZWM5ZDQ0MWMiLCJ7XCJpYXRcIjoxNTg1MDEwODIxLFwiZXh0XCI6MTkwMDQxMDgyMSxcImlzc1wiOlwiXFxcImRpZDpldGhyOjB4MDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMFxcXCJcIixcInN1YlwiOlwiNnRGWFRmUnh5a3dNS09PalNNYmRQckVNcnBVbDNtM2o4RFF5Y0ZxTzJ0dz1cIixcImF1ZFwiOlwiZGlkOm1hZ2ljOjMzZjAxNGVlLTNkZDUtNGRmZi1iYzE2LTgxNTU3MTFiN2UwMlwiLFwibmJmXCI6MTU4NTAxMDgyMSxcInRpZFwiOlwiOGEzYjdkZDUtZTFjZi00OTY1LWFlMmItZDIwZjE4OGU2ZWMyXCIsXCJhZGRcIjpcIjB4OTFmYmU3NGJlNmM2YmZkOGRkZGRkOTMwMTFiMDU5YjkyNTNmMTA3ODU2NDk3MzhiYTIxN2U1MWUwZTNkZjEzODFkMjBmNTIxYTM2NDFmMjNlYjk5Y2NiMzRlM2JjNWQ5NjMzMmZkZWJjOGVmYTUwY2RiNDE1ZTQ1NTAwOTUyY2QxY1wifSJd'; 45 | 46 | export const EXPIRED_DIDT = 47 | 'WyIweGE3MDUzYzg3OTI2ZjMzZDBjMTZiMjMyYjYwMWYxZDc2NmRiNWY3YWM4MTg2MzUyMzY4ZjAyMzIyMGEwNzJjYzkzM2JjYjI2MmU4ODQyNWViZDA0MzcyZGU3YTc0NzMwYjRmYWYzOGU0ZjgwNmYzOTJjMTVkNzY2YmVkMjVlZmUxMWIiLCJ7XCJpYXRcIjoxNTg1MDEwODM1LFwiZXh0XCI6MTU4NTAxMDgzNixcImlzc1wiOlwiZGlkOmV0aHI6MHhCMmVjOWI2MTY5OTc2MjQ5MWI2NTQyMjc4RTlkRkVDOTA1MGY4MDg5XCIsXCJzdWJcIjpcIjZ0RlhUZlJ4eWt3TUtPT2pTTWJkUHJFTXJwVWwzbTNqOERReWNGcU8ydHc9XCIsXCJhdWRcIjpcImRpZDptYWdpYzpkNGMwMjgxYi04YzViLTQ5NDMtODUwOS0xNDIxNzUxYTNjNzdcIixcIm5iZlwiOjE1ODUwMTA4MzUsXCJ0aWRcIjpcImFjMmE4YzFjLWE4OWEtNDgwOC1hY2QxLWM1ODg1ZTI2YWZiY1wiLFwiYWRkXCI6XCIweDkxZmJlNzRiZTZjNmJmZDhkZGRkZDkzMDExYjA1OWI5MjUzZjEwNzg1NjQ5NzM4YmEyMTdlNTFlMGUzZGYxMzgxZDIwZjUyMWEzNjQxZjIzZWI5OWNjYjM0ZTNiYzVkOTYzMzJmZGViYzhlZmE1MGNkYjQxNWU0NTUwMDk1MmNkMWNcIn0iXQ=='; 48 | 49 | export const VALID_ATTACHMENT_DIDT = 50 | 'WyIweGVkMWMwNWRlMTVlMWFkY2Y5ZmEyZWNkNjVjZjg5NWMzYTgzMzQ2OGMwOGFhMmE3YjQ5ZDgyMjFiZWEyMWU1YjgzNDRiNWEwMzAzNmQxMzA5MzQyNTgzMWIxZTFjZGIwZWQ2NTgyMDI4MWU1NzhlMjU5ODJhYzdkYmNkZWJhN2I1MWMiLCJ7XCJpYXRcIjoxNjg4MDYzMTA4LFwiZXh0XCI6MS4wMDAwMDAwMDAwMDE2ODgxZSsyMSxcImlzc1wiOlwiZGlkOmV0aHI6MHhhMWI0YzA5NDI2NDdlNzkwY0ZEMmEwNUE1RkQyNkMwMmM0MjEzOWFlXCIsXCJzdWJcIjpcIjhaTUJnOXNwMFgwQ0FNanhzcVFaOGRzRTJwNVlZWm9lYkRPeWNPUFNNbDA9XCIsXCJhdWRcIjpcIjN3X216VmktaDNtUzc3cFZ4b19ydlJhWjR2WXpOZ0Vudm05ZGcwWnkzYzg9XCIsXCJuYmZcIjoxNjg4MDYzMTA4LFwidGlkXCI6XCJjM2U5ZWRiYy04MDU2LTQ3NGItOGFkMy1hOGI2MzM3NThlOTRcIixcImFkZFwiOlwiMHgzZGExZTM3MmU1ZWU5MjI4YzdlYjBkNmQwZDE2MTAxZjBkNjE5MDY0ODVhYjgzNDMzNWI3Y2YxOGE5ZDNmZWEzNjRmYzFjMTFiNzRlYzBhNTQ0ZTkzNmJkNjQ1Y2U3ZDdkZTIyMTRlNTJlYjZhOThjZTIyNzI1OTEwNDg0ZjJkOTFjXCJ9Il0'; 51 | -------------------------------------------------------------------------------- /test/lib/factories.ts: -------------------------------------------------------------------------------- 1 | import { API_FULL_URL, API_KEY } from './constants'; 2 | import { MagicAdminSDK } from '../../src/core/sdk'; 3 | 4 | export function createMagicAdminSDK(endpoint = API_FULL_URL, clientId = null) { 5 | return new MagicAdminSDK(API_KEY, { endpoint, clientId }); 6 | } 7 | -------------------------------------------------------------------------------- /test/spec/core/sdk-exceptions/error-factories.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-underscore-dangle */ 2 | import { 3 | MagicAdminSDKError, 4 | createTokenExpiredError, 5 | createIncorrectSignerAddressError, 6 | createFailedRecoveringProofError, 7 | createApiKeyMissingError, 8 | createServiceError, 9 | createExpectedBearerStringError, 10 | createTokenCannotBeUsedYetError, 11 | createAudienceMismatchError, 12 | } from '../../../../src/core/sdk-exceptions'; 13 | 14 | function errorAssertions( 15 | error: MagicAdminSDKError, 16 | expectedCode: string, 17 | expectedMessage: string, 18 | expectedData: any[] = [], 19 | ) { 20 | expect(error instanceof MagicAdminSDKError).toBe(true); 21 | expect(error.code).toBe(expectedCode); 22 | expect(error.message).toBe(`Magic Admin SDK Error: [${expectedCode}] ${expectedMessage}`); 23 | expect(error.data).toEqual(expectedData); 24 | } 25 | 26 | test('Creates `ERROR_DIDT_EXPIRED` error', async () => { 27 | const error = createTokenExpiredError(); 28 | errorAssertions(error, 'ERROR_DIDT_EXPIRED', 'DID Token has expired. Request failed authentication.'); 29 | }); 30 | 31 | test('Creates `ERROR_DIDT_CANNOT_BE_USED_YET` error', async () => { 32 | const error = createTokenCannotBeUsedYetError(); 33 | errorAssertions( 34 | error, 35 | 'ERROR_DIDT_CANNOT_BE_USED_YET', 36 | 'Given DID Token cannot be used at this time. Please check the `nbf` field and regenerate a new token with a suitable value.', 37 | ); 38 | }); 39 | 40 | test('Creates `ERROR_INCORRECT_SIGNER_ADDR` error', async () => { 41 | const error = createIncorrectSignerAddressError(); 42 | errorAssertions( 43 | error, 44 | 'ERROR_INCORRECT_SIGNER_ADDR', 45 | 'Incorrect signer address for DID Token. Request failed authentication.', 46 | ); 47 | }); 48 | 49 | test('Creates `ERROR_FAILED_RECOVERING_PROOF` error', async () => { 50 | const error = createFailedRecoveringProofError(); 51 | errorAssertions(error, 'ERROR_FAILED_RECOVERING_PROOF', 'Failed to recover proof. Request failed authentication.'); 52 | }); 53 | 54 | test('Creates `ERROR_SECRET_API_KEY_MISSING` error', async () => { 55 | const error = createApiKeyMissingError(); 56 | errorAssertions( 57 | error, 58 | 'ERROR_SECRET_API_KEY_MISSING', 59 | 'Please provide a secret Magic API key that you acquired from the developer dashboard.', 60 | ); 61 | }); 62 | 63 | test('Creates `SERVICE_ERROR` error with empty `data` property', async () => { 64 | const error = createServiceError(); 65 | errorAssertions( 66 | error, 67 | 'SERVICE_ERROR', 68 | 'A service error occurred while communicating with the Magic API. Check the `data` key of this error object to see nested errors with additional context.', 69 | ); 70 | }); 71 | 72 | test('Creates `SERVICE_ERROR` error with non-empty `data` property', async () => { 73 | const error = createServiceError('hello', 'world'); 74 | errorAssertions( 75 | error, 76 | 'SERVICE_ERROR', 77 | 'A service error occurred while communicating with the Magic API. Check the `data` key of this error object to see nested errors with additional context.', 78 | ['hello', 'world'], 79 | ); 80 | }); 81 | 82 | test('Creates `EXPECTED_BEARER_STRING` error', async () => { 83 | const error = createExpectedBearerStringError(); 84 | errorAssertions(error, 'EXPECTED_BEARER_STRING', 'Expected argument to be a string in the `Bearer {token}` format.'); 85 | }); 86 | 87 | test('Creates `AUDIENCE_MISMATCH` error', async () => { 88 | const error = createAudienceMismatchError(); 89 | errorAssertions( 90 | error, 91 | 'ERROR_AUDIENCE_MISMATCH', 92 | 'Audience does not match client ID. Please ensure your secret key matches the application which generated the DID token.', 93 | ); 94 | }); 95 | -------------------------------------------------------------------------------- /test/spec/core/sdk-exceptions/magic-admin-sdk-error/constructor.spec.ts: -------------------------------------------------------------------------------- 1 | import { MagicAdminSDKError } from '../../../../../src/core/sdk-exceptions'; 2 | 3 | test('Instantiates `MagicAdminSDKError` with empty `data` property', () => { 4 | const error = new MagicAdminSDKError('TEST_CODE' as any, 'test message'); 5 | expect(error instanceof MagicAdminSDKError).toBe(true); 6 | expect(error.message).toBe('Magic Admin SDK Error: [TEST_CODE] test message'); 7 | expect(error.code).toBe('TEST_CODE'); 8 | expect(error.data).toEqual([]); 9 | }); 10 | 11 | test('Instantiates `MagicAdminSDKError` with non-empty `data` property', () => { 12 | const error = new MagicAdminSDKError('TEST_CODE' as any, 'test message', ['hello world']); 13 | expect(error instanceof MagicAdminSDKError).toBe(true); 14 | expect(error.message).toBe('Magic Admin SDK Error: [TEST_CODE] test message'); 15 | expect(error.code).toBe('TEST_CODE'); 16 | expect(error.data).toEqual(['hello world']); 17 | }); 18 | -------------------------------------------------------------------------------- /test/spec/core/sdk/constructor.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-new */ 2 | import { Magic } from '../../../../src/index'; 3 | import { API_FULL_URL, API_KEY } from '../../../lib/constants'; 4 | import { TokenModule } from '../../../../src/modules/token'; 5 | import { UsersModule } from '../../../../src/modules/users'; 6 | import { UtilsModule } from '../../../../src/modules/utils'; 7 | import { get } from '../../../../src/utils/rest'; 8 | import { createApiKeyMissingError } from '../../../../src/core/sdk-exceptions'; 9 | 10 | test('Initialize `MagicAdminSDK`', () => { 11 | const magic = new Magic(API_KEY); 12 | 13 | expect(magic.secretApiKey).toBe(API_KEY); 14 | expect(magic.apiBaseUrl).toBe(API_FULL_URL); 15 | expect(magic.token instanceof TokenModule).toBe(true); 16 | expect(magic.users instanceof UsersModule).toBe(true); 17 | }); 18 | 19 | test('Initialize `MagicAdminSDK` with custom endpoint', () => { 20 | const magic = new Magic(API_KEY, { endpoint: 'https://example.com' }); 21 | 22 | expect(magic.secretApiKey).toBe(API_KEY); 23 | expect(magic.apiBaseUrl).toBe('https://example.com'); 24 | expect(magic.token instanceof TokenModule).toBe(true); 25 | expect(magic.users instanceof UsersModule).toBe(true); 26 | expect(magic.utils instanceof UtilsModule).toBe(true); 27 | }); 28 | 29 | test('Strips trailing slash(es) from custom endpoint argument', () => { 30 | const magicA = new Magic(API_KEY, { endpoint: 'https://example.com/' }); 31 | const magicB = new Magic(API_KEY, { endpoint: 'https://example.com//' }); 32 | const magicC = new Magic(API_KEY, { endpoint: 'https://example.com///' }); 33 | 34 | expect(magicA.apiBaseUrl).toBe('https://example.com'); 35 | expect(magicB.apiBaseUrl).toBe('https://example.com'); 36 | expect(magicC.apiBaseUrl).toBe('https://example.com'); 37 | }); 38 | 39 | test('Initialize `MagicAdminSDK` using static init and empty options', async () => { 40 | const successRes = Promise.resolve({ 41 | client_id: 'foo', 42 | app_scope: 'GLOBAL', 43 | }); 44 | (get as any) = jest.fn().mockImplementation(() => successRes); 45 | 46 | const magic = await Magic.init(API_KEY, {}); 47 | 48 | expect(magic.secretApiKey).toBe(API_KEY); 49 | expect(magic.apiBaseUrl).toBe(API_FULL_URL); 50 | expect(magic.token instanceof TokenModule).toBe(true); 51 | expect(magic.users instanceof UsersModule).toBe(true); 52 | }); 53 | 54 | test('Initialize `MagicAdminSDK` using static init and undefined options', async () => { 55 | const successRes = Promise.resolve({ 56 | client_id: 'foo', 57 | app_scope: 'GLOBAL', 58 | }); 59 | (get as any) = jest.fn().mockImplementation(() => successRes); 60 | 61 | const magic = await Magic.init(API_KEY); 62 | 63 | expect(magic.secretApiKey).toBe(API_KEY); 64 | expect(magic.apiBaseUrl).toBe(API_FULL_URL); 65 | expect(magic.token instanceof TokenModule).toBe(true); 66 | expect(magic.users instanceof UsersModule).toBe(true); 67 | }); 68 | 69 | test('Initialize `MagicAdminSDK` using static init and client ID', async () => { 70 | const magic = await Magic.init(API_KEY, { clientId: '1234' }); 71 | 72 | expect(magic.secretApiKey).toBe(API_KEY); 73 | expect(magic.apiBaseUrl).toBe(API_FULL_URL); 74 | expect(magic.token instanceof TokenModule).toBe(true); 75 | expect(magic.users instanceof UsersModule).toBe(true); 76 | }); 77 | 78 | test('Initialize `MagicAdminSDK` using static init and endpoint', async () => { 79 | const successRes = Promise.resolve({ 80 | client_id: 'foo', 81 | app_scope: 'GLOBAL', 82 | }); 83 | (get as any) = jest.fn().mockImplementation(() => successRes); 84 | 85 | const magic = await Magic.init(API_KEY, { endpoint: 'https://example.com' }); 86 | 87 | expect(magic.secretApiKey).toBe(API_KEY); 88 | expect(magic.apiBaseUrl).toBe('https://example.com'); 89 | expect(magic.token instanceof TokenModule).toBe(true); 90 | expect(magic.users instanceof UsersModule).toBe(true); 91 | }); 92 | 93 | test('Initialize `MagicAdminSDK` missing API Key', async () => { 94 | const expectedError = createApiKeyMissingError(); 95 | expect(Magic.init(null, { clientId: '1234' })).rejects.toThrow(expectedError); 96 | }); 97 | -------------------------------------------------------------------------------- /test/spec/index.spec.ts: -------------------------------------------------------------------------------- 1 | import * as MagicAdmin from '../../src/index'; 2 | 3 | describe('MagicAdmin', () => { 4 | it('should have exports', () => { 5 | expect(MagicAdmin).toEqual(expect.any(Object)); 6 | }); 7 | 8 | it('should not have undefined exports', () => { 9 | for (const k of Object.keys(MagicAdmin)) 10 | expect(MagicAdmin).not.toHaveProperty(k, undefined); 11 | }); 12 | }); -------------------------------------------------------------------------------- /test/spec/modules/base-module/constructor.spec.ts: -------------------------------------------------------------------------------- 1 | import { MagicAdminSDK } from '../../../../src/core/sdk'; 2 | import { BaseModule } from '../../../../src/modules/base-module'; 3 | import { createMagicAdminSDK } from '../../../lib/factories'; 4 | 5 | test('Initializes `BaseModule`', () => { 6 | const sdk = createMagicAdminSDK(); 7 | 8 | const baseModule: any = new (BaseModule as any)(sdk); 9 | 10 | expect(baseModule instanceof BaseModule).toBe(true); 11 | expect(baseModule.sdk instanceof MagicAdminSDK).toBe(true); 12 | }); 13 | -------------------------------------------------------------------------------- /test/spec/modules/token/decode.spec.ts: -------------------------------------------------------------------------------- 1 | import { createMagicAdminSDK } from '../../../lib/factories'; 2 | import { VALID_DIDT, VALID_DIDT_DECODED, INVALID_DIDT_MALFORMED_CLAIM } from '../../../lib/constants'; 3 | import { createMalformedTokenError } from '../../../../src/core/sdk-exceptions'; 4 | 5 | test('Successfully decodes DIDT', async () => { 6 | const sdk = createMagicAdminSDK(); 7 | const result = sdk.token.decode(VALID_DIDT); 8 | expect(result).toEqual(VALID_DIDT_DECODED); 9 | }); 10 | 11 | test('Throws error if token is malformed', async () => { 12 | const sdk = createMagicAdminSDK(); 13 | const expectedError = createMalformedTokenError(); 14 | expect(() => sdk.token.decode(INVALID_DIDT_MALFORMED_CLAIM)).toThrow(expectedError); 15 | }); 16 | -------------------------------------------------------------------------------- /test/spec/modules/token/getIssuer.spec.ts: -------------------------------------------------------------------------------- 1 | import { createMagicAdminSDK } from '../../../lib/factories'; 2 | import { VALID_DIDT, VALID_DIDT_PARSED_CLAIMS } from '../../../lib/constants'; 3 | 4 | test('Successfully gets issuer from DIDT', () => { 5 | const sdk = createMagicAdminSDK(); 6 | const result = sdk.token.getIssuer(VALID_DIDT); 7 | const expected = VALID_DIDT_PARSED_CLAIMS.iss; 8 | expect(result).toBe(expected); 9 | }); 10 | -------------------------------------------------------------------------------- /test/spec/modules/token/getPublicAddress.spec.ts: -------------------------------------------------------------------------------- 1 | import { createMagicAdminSDK } from '../../../lib/factories'; 2 | import { VALID_DIDT, VALID_DIDT_PARSED_CLAIMS } from '../../../lib/constants'; 3 | 4 | test('Successfully gets public address from DIDT', () => { 5 | const sdk = createMagicAdminSDK(); 6 | const result = sdk.token.getPublicAddress(VALID_DIDT); 7 | const expected = VALID_DIDT_PARSED_CLAIMS.iss.split(':')[2]; 8 | expect(result).toBe(expected); 9 | }); 10 | -------------------------------------------------------------------------------- /test/spec/modules/token/validate.spec.ts: -------------------------------------------------------------------------------- 1 | import { createMagicAdminSDK } from '../../../lib/factories'; 2 | import { 3 | VALID_DIDT, 4 | VALID_DIDT_WITH_INVALID_RECOVERY_BIT, 5 | INVALID_SIGNER_DIDT, 6 | EXPIRED_DIDT, 7 | INVALID_DIDT_MALFORMED_CLAIM, 8 | VALID_FUTURE_MARKED_DIDT, 9 | VALID_ATTACHMENT_DIDT, 10 | } from '../../../lib/constants'; 11 | import { 12 | createIncorrectSignerAddressError, 13 | createTokenExpiredError, 14 | createFailedRecoveringProofError, 15 | createMalformedTokenError, 16 | createTokenCannotBeUsedYetError, 17 | createAudienceMismatchError, 18 | } from '../../../../src/core/sdk-exceptions'; 19 | 20 | test('Successfully validates DIDT', async () => { 21 | const sdk = createMagicAdminSDK(undefined, 'did:magic:f54168e9-9ce9-47f2-81c8-7cb2a96b26ba'); 22 | expect(() => sdk.token.validate(VALID_DIDT)).not.toThrow(); 23 | }); 24 | 25 | test('Successfully validates DIDT without checking audience', async () => { 26 | const sdk = createMagicAdminSDK(); 27 | expect(() => sdk.token.validate(VALID_DIDT)).not.toThrow(); 28 | }); 29 | 30 | test('Successfully validates DIDT with attachment', async () => { 31 | const sdk = createMagicAdminSDK(); 32 | expect(() => sdk.token.validate(VALID_ATTACHMENT_DIDT, 'ravi@magic.link')).not.toThrow(); 33 | }); 34 | 35 | test('Fails when signer address mismatches signature', async () => { 36 | const sdk = createMagicAdminSDK(); 37 | const expectedError = createIncorrectSignerAddressError(); 38 | expect(() => sdk.token.validate(INVALID_SIGNER_DIDT)).toThrow(expectedError); 39 | }); 40 | 41 | test('Fails when given expired token', async () => { 42 | const sdk = createMagicAdminSDK(); 43 | const expectedError = createTokenExpiredError(); 44 | expect(() => sdk.token.validate(EXPIRED_DIDT)).toThrow(expectedError); 45 | }); 46 | 47 | test('Fails when given a token with a future `nbf` timestamp', async () => { 48 | const sdk = createMagicAdminSDK(); 49 | const expectedError = createTokenCannotBeUsedYetError(); 50 | expect(() => sdk.token.validate(VALID_FUTURE_MARKED_DIDT)).toThrow(expectedError); 51 | }); 52 | 53 | test('Fails if signature recovery rejects', async () => { 54 | const sdk = createMagicAdminSDK(); 55 | const expectedError = createFailedRecoveringProofError(); 56 | expect(() => sdk.token.validate(VALID_DIDT_WITH_INVALID_RECOVERY_BIT)).toThrow(expectedError); 57 | }); 58 | 59 | test('Fails if decoding token fails', async () => { 60 | const sdk = createMagicAdminSDK(); 61 | const expectedError = createMalformedTokenError(); 62 | expect(() => sdk.token.validate(INVALID_DIDT_MALFORMED_CLAIM)).toThrow(expectedError); 63 | }); 64 | 65 | test('Fails if aud is incorrect', async () => { 66 | const sdk = createMagicAdminSDK(undefined, 'different'); 67 | const expectedError = createAudienceMismatchError(); 68 | expect(() => sdk.token.validate(VALID_DIDT)).toThrow(expectedError); 69 | }); 70 | -------------------------------------------------------------------------------- /test/spec/modules/users/getMetadataByIssuer.spec.ts: -------------------------------------------------------------------------------- 1 | import { createApiKeyMissingError } from '../../../../src/core/sdk-exceptions'; 2 | import { WalletType } from '../../../../src/types/wallet-types'; 3 | import { get } from '../../../../src/utils/rest'; 4 | import { API_KEY } from '../../../lib/constants'; 5 | import { createMagicAdminSDK } from '../../../lib/factories'; 6 | 7 | const successRes = Promise.resolve({ 8 | issuer: 'foo', 9 | public_address: 'bar', 10 | email: 'baz', 11 | oauth_provider: 'foo1', 12 | phone_number: '+1234', 13 | username: 'buzz', 14 | }); 15 | const successResWithWallets = Promise.resolve({ 16 | issuer: 'foo', 17 | public_address: 'bar', 18 | email: 'baz', 19 | oauth_provider: 'foo1', 20 | phone_number: '+1234', 21 | username: 'buzz', 22 | wallets: [ 23 | { 24 | wallet_type: 'SOLANA', 25 | network: 'MAINNET', 26 | public_address: 'barxyz', 27 | }, 28 | ], 29 | }); 30 | const nullRes = Promise.resolve({}); 31 | 32 | test('Successfully GETs to metadata endpoint via issuer', async () => { 33 | const sdk = createMagicAdminSDK('https://example.com'); 34 | 35 | const getStub = jest.fn().mockImplementation(() => successRes); 36 | (get as any) = getStub; 37 | 38 | const result = await sdk.users.getMetadataByIssuer('did:ethr:0x1234'); 39 | 40 | console.log(result); 41 | 42 | const getArguments = getStub.mock.calls[0]; 43 | expect(getArguments).toEqual([ 44 | 'https://example.com/v1/admin/auth/user/get', 45 | API_KEY, 46 | { issuer: 'did:ethr:0x1234', wallet_type: 'NONE' }, 47 | ]); 48 | 49 | expect(result).toEqual({ 50 | issuer: 'foo', 51 | publicAddress: 'bar', 52 | email: 'baz', 53 | oauthProvider: 'foo1', 54 | phoneNumber: '+1234', 55 | username: 'buzz', 56 | wallets: null, 57 | }); 58 | }); 59 | 60 | test('Successfully GETs `null` metadata endpoint via issuer', async () => { 61 | const sdk = createMagicAdminSDK('https://example.com'); 62 | 63 | const getStub = jest.fn().mockImplementation(() => nullRes); 64 | (get as any) = getStub; 65 | 66 | const result = await sdk.users.getMetadataByIssuer('did:ethr:0x1234'); 67 | 68 | const getArguments = getStub.mock.calls[0]; 69 | expect(getArguments).toEqual([ 70 | 'https://example.com/v1/admin/auth/user/get', 71 | API_KEY, 72 | { issuer: 'did:ethr:0x1234', wallet_type: 'NONE' }, 73 | ]); 74 | expect(result).toEqual({ 75 | issuer: null, 76 | publicAddress: null, 77 | email: null, 78 | oauthProvider: null, 79 | phoneNumber: null, 80 | username: null, 81 | wallets: null, 82 | }); 83 | }); 84 | 85 | test('Fails GET if API key is missing', async () => { 86 | const sdk = createMagicAdminSDK('https://example.com'); 87 | (sdk as any).secretApiKey = undefined; 88 | 89 | const getStub = jest.fn().mockImplementation(); 90 | (get as any) = getStub; 91 | 92 | const expectedError = createApiKeyMissingError(); 93 | expect(sdk.users.getMetadataByIssuer('did:ethr:0x1234')).rejects.toThrow(expectedError); 94 | 95 | expect(getStub).not.toBeCalled(); 96 | }); 97 | 98 | test('Successfully GETs to metadata endpoint via issuer and wallet type', async () => { 99 | const sdk = createMagicAdminSDK('https://example.com'); 100 | 101 | const getStub = jest.fn().mockImplementation(() => successResWithWallets); 102 | (get as any) = getStub; 103 | 104 | const result = await sdk.users.getMetadataByIssuerAndWallet('did:ethr:0x1234', WalletType.SOLANA); 105 | 106 | const getArguments = getStub.mock.calls[0]; 107 | expect(getArguments).toEqual([ 108 | 'https://example.com/v1/admin/auth/user/get', 109 | API_KEY, 110 | { issuer: 'did:ethr:0x1234', wallet_type: 'SOLANA'}, 111 | ]); 112 | expect(result).toEqual({ 113 | issuer: 'foo', 114 | publicAddress: 'bar', 115 | email: 'baz', 116 | oauthProvider: 'foo1', 117 | phoneNumber: '+1234', 118 | username: 'buzz', 119 | wallets: [ 120 | { 121 | wallet_type: 'SOLANA', 122 | network: 'MAINNET', 123 | public_address: 'barxyz', 124 | }, 125 | ], 126 | }); 127 | }); 128 | -------------------------------------------------------------------------------- /test/spec/modules/users/getMetadataByPublicAddress.spec.ts: -------------------------------------------------------------------------------- 1 | import { WalletType } from '../../../../src/types/wallet-types'; 2 | import { createMagicAdminSDK } from '../../../lib/factories'; 3 | 4 | test('Successfully GETs to metadata endpoint via public address', async () => { 5 | const sdk = createMagicAdminSDK('https://example.com'); 6 | 7 | const getMetadataStub = jest.fn().mockImplementation(() => Promise.resolve()); 8 | (sdk.users.getMetadataByIssuer as any) = getMetadataStub; 9 | 10 | await expect(sdk.users.getMetadataByPublicAddress('0x1234')).resolves.not.toThrow(); 11 | 12 | const getMetadataArguments = getMetadataStub.mock.calls[0]; 13 | expect(getMetadataArguments).toEqual(['did:ethr:0x1234']); 14 | }); 15 | 16 | test('Successfully GETs to metadata endpoint via public address and wallet type', async () => { 17 | const sdk = createMagicAdminSDK('https://example.com'); 18 | 19 | const getMetadataStub = jest.fn().mockImplementation(() => Promise.resolve()); 20 | (sdk.users.getMetadataByIssuerAndWallet as any) = getMetadataStub; 21 | 22 | await expect(sdk.users.getMetadataByPublicAddressAndWallet('0x1234', WalletType.ANY)).resolves.not.toThrow(); 23 | 24 | const getMetadataArguments = getMetadataStub.mock.calls[0]; 25 | expect(getMetadataArguments).toEqual(['did:ethr:0x1234', 'ANY']); 26 | }); 27 | -------------------------------------------------------------------------------- /test/spec/modules/users/getMetadataByToken.spec.ts: -------------------------------------------------------------------------------- 1 | import { createMagicAdminSDK } from '../../../lib/factories'; 2 | import { VALID_DIDT, VALID_DIDT_PARSED_CLAIMS } from '../../../lib/constants'; 3 | import { WalletType } from '../../../../src/types/wallet-types'; 4 | 5 | test('Successfully GETs to metadata endpoint via public address', async () => { 6 | const sdk = createMagicAdminSDK('https://example.com'); 7 | 8 | const getMetadataStub = jest.fn().mockImplementation(() => Promise.resolve()); 9 | (sdk.users.getMetadataByIssuer as any) = getMetadataStub; 10 | 11 | await expect(sdk.users.getMetadataByToken(VALID_DIDT)).resolves.not.toThrow(); 12 | 13 | const getMetadataArguments = getMetadataStub.mock.calls[0]; 14 | expect(getMetadataArguments).toEqual([VALID_DIDT_PARSED_CLAIMS.iss]); 15 | }); 16 | 17 | test('Successfully GETs to metadata endpoint via public address and wallet type', async () => { 18 | const sdk = createMagicAdminSDK('https://example.com'); 19 | 20 | const getMetadataStub = jest.fn().mockImplementation(() => Promise.resolve()); 21 | (sdk.users.getMetadataByIssuerAndWallet as any) = getMetadataStub; 22 | 23 | await expect(sdk.users.getMetadataByTokenAndWallet(VALID_DIDT, WalletType.ALGOD)).resolves.not.toThrow(); 24 | 25 | const getMetadataArguments = getMetadataStub.mock.calls[0]; 26 | expect(getMetadataArguments).toEqual([VALID_DIDT_PARSED_CLAIMS.iss, 'ALGOD']); 27 | }); 28 | 29 | test('Successfully GETs to metadata endpoint via public address and none wallet type', async () => { 30 | const sdk = createMagicAdminSDK('https://example.com'); 31 | 32 | const getMetadataStub = jest.fn().mockImplementation(() => Promise.resolve()); 33 | (sdk.users.getMetadataByIssuerAndWallet as any) = getMetadataStub; 34 | 35 | await expect(sdk.users.getMetadataByTokenAndWallet(VALID_DIDT, WalletType.NONE)).resolves.not.toThrow(); 36 | 37 | const getMetadataArguments = getMetadataStub.mock.calls[0]; 38 | expect(getMetadataArguments).toEqual([VALID_DIDT_PARSED_CLAIMS.iss, 'NONE']); 39 | }); 40 | -------------------------------------------------------------------------------- /test/spec/modules/users/logoutByIssuer.spec.ts: -------------------------------------------------------------------------------- 1 | import { createMagicAdminSDK } from '../../../lib/factories'; 2 | import { API_KEY } from '../../../lib/constants'; 3 | import { createApiKeyMissingError } from '../../../../src/core/sdk-exceptions'; 4 | import { post } from '../../../../src/utils/rest'; 5 | 6 | test('Successfully POSTs to logout endpoint via DIDT', async () => { 7 | const sdk = createMagicAdminSDK('https://example.com'); 8 | 9 | const postStub = jest.fn(); 10 | (post as any) = postStub; 11 | 12 | await expect(sdk.users.logoutByIssuer('did:ethr:0x1234')).resolves.not.toThrow(); 13 | 14 | const postArguments = postStub.mock.calls[0]; 15 | expect(postArguments).toEqual([ 16 | 'https://example.com/v2/admin/auth/user/logout', 17 | API_KEY, 18 | { issuer: 'did:ethr:0x1234' }, 19 | ]); 20 | }); 21 | 22 | test('Fails POST if API key is missing', async () => { 23 | const sdk = createMagicAdminSDK('https://example.com'); 24 | (sdk as any).secretApiKey = undefined; 25 | 26 | const postStub = jest.fn(); 27 | (post as any) = postStub; 28 | 29 | const expectedError = createApiKeyMissingError(); 30 | 31 | await expect(sdk.users.logoutByIssuer('did:ethr:0x1234')).rejects.toThrow(expectedError); 32 | 33 | expect(postStub).not.toBeCalled(); 34 | }); 35 | -------------------------------------------------------------------------------- /test/spec/modules/users/logoutByPublicAddress.spec.ts: -------------------------------------------------------------------------------- 1 | import { createMagicAdminSDK } from '../../../lib/factories'; 2 | 3 | test('Successfully GETs to metadata endpoint via public address', async () => { 4 | const sdk = createMagicAdminSDK('https://example.com'); 5 | 6 | const logoutStub = jest.fn().mockImplementation(() => Promise.resolve()); 7 | (sdk.users.logoutByIssuer as any) = logoutStub; 8 | 9 | await expect(sdk.users.logoutByPublicAddress('0x1234')).resolves.not.toThrow(); 10 | 11 | const logoutArguments = logoutStub.mock.calls[0]; 12 | expect(logoutArguments).toEqual(['did:ethr:0x1234']); 13 | }); 14 | -------------------------------------------------------------------------------- /test/spec/modules/users/logoutByToken.spec.ts: -------------------------------------------------------------------------------- 1 | import { createMagicAdminSDK } from '../../../lib/factories'; 2 | import { VALID_DIDT, VALID_DIDT_PARSED_CLAIMS } from '../../../lib/constants'; 3 | 4 | test('Successfully GETs to metadata endpoint via public address', async () => { 5 | const sdk = createMagicAdminSDK('https://example.com'); 6 | 7 | const logoutStub = jest.fn().mockImplementation(() => Promise.resolve()); 8 | (sdk.users.logoutByIssuer as any) = logoutStub; 9 | 10 | await expect(sdk.users.logoutByToken(VALID_DIDT)).resolves.not.toThrow(); 11 | 12 | const logoutArguments = logoutStub.mock.calls[0]; 13 | expect(logoutArguments).toEqual([VALID_DIDT_PARSED_CLAIMS.iss]); 14 | }); 15 | -------------------------------------------------------------------------------- /test/spec/modules/utils/parseAuthorizationHeader.spec.ts: -------------------------------------------------------------------------------- 1 | import { createMagicAdminSDK } from '../../../lib/factories'; 2 | import { VALID_DIDT } from '../../../lib/constants'; 3 | import { createExpectedBearerStringError } from '../../../../src/core/sdk-exceptions'; 4 | 5 | test('Successfully parses raw DIDT from `Bearer` authorization header', async () => { 6 | const sdk = createMagicAdminSDK(); 7 | const result = sdk.utils.parseAuthorizationHeader(`Bearer ${VALID_DIDT}`); 8 | expect(result).toEqual(VALID_DIDT); 9 | }); 10 | 11 | test('Raises error if header is in the wrong format', async () => { 12 | const sdk = createMagicAdminSDK(); 13 | const expectedError = createExpectedBearerStringError(); 14 | expect(() => sdk.utils.parseAuthorizationHeader(`Ooops ${VALID_DIDT}`)).toThrow(expectedError); 15 | }); 16 | -------------------------------------------------------------------------------- /test/spec/modules/utils/validateTokenOwnership.spec.ts: -------------------------------------------------------------------------------- 1 | import { createMagicAdminSDK } from '../../../lib/factories'; 2 | 3 | jest.mock('ethers', () => { 4 | const originalModule = jest.requireActual('ethers'); 5 | return { 6 | ...originalModule, 7 | ethers: { 8 | ...originalModule.ethers, 9 | Contract: jest.fn(() => ({ 10 | balanceOf: jest.fn().mockImplementation((walletAddress: string, tokenId?: string) => { 11 | if (tokenId === '2') { 12 | return BigInt(1); // User owns token 13 | } else { 14 | return BigInt(0); // User doesn't own token 15 | } 16 | }), 17 | })), 18 | }, 19 | }; 20 | }); 21 | 22 | 23 | test('Throws an error if ERC1155 and no token provided', async () => { 24 | const sdk = createMagicAdminSDK('https://example.com'); 25 | 26 | await expect(sdk.utils.validateTokenOwnership('did:ethr:0x123', '0xfoo', 'ERC1155', 'https://example.com')).rejects.toThrow( 27 | 'ERC1155 requires a tokenId', 28 | ); 29 | }); 30 | 31 | test('Returns an error if DID token is malformed', async () => { 32 | const sdk = createMagicAdminSDK('https://example.com'); 33 | 34 | // Mock the magic token validation by setting the code to ERROR_MALFORMED_TOKEN 35 | sdk.token.validate = jest.fn().mockRejectedValue({ code: 'ERROR_MALFORMED_TOKEN' }); 36 | 37 | await expect(sdk.utils.validateTokenOwnership('did:ethr:0x123', '0xfoo', 'ERC1155', 'https://example.com', '1')).resolves.toEqual({ 38 | valid: false, 39 | error_code: 'UNAUTHORIZED', 40 | message: 'Invalid DID token: ERROR_MALFORMED_TOKEN', 41 | }); 42 | }); 43 | 44 | test('Returns an error if DID token is expired', async () => { 45 | const sdk = createMagicAdminSDK('https://example.com'); 46 | 47 | // Mock the magic token validation by setting the code to ERROR_DIDT_EXPIRED 48 | sdk.token.validate = jest.fn().mockRejectedValue({ code: 'ERROR_DIDT_EXPIRED' }); 49 | 50 | await expect(sdk.utils.validateTokenOwnership('did:ethr:0x123', '0xfoo', 'ERC1155', 'https://example.com', '1')).resolves.toEqual({ 51 | valid: false, 52 | error_code: 'UNAUTHORIZED', 53 | message: 'Invalid DID token: ERROR_DIDT_EXPIRED', 54 | }); 55 | }); 56 | 57 | test('Throws an error if DID token validation returns unexpected error code', async () => { 58 | const sdk = createMagicAdminSDK('https://example.com'); 59 | 60 | // Mock the magic token validation by setting the code to ERROR_MALFORMED_TOKEN 61 | sdk.token.validate = jest.fn().mockRejectedValue({ code: 'UNKNOWN' }); 62 | 63 | await expect(sdk.utils.validateTokenOwnership('did:ethr:0x123', '0xfoo', 'ERC1155', 'https://example.com', '1')).rejects.toThrow(); 64 | }); 65 | 66 | test('Returns an error if ERC721 token is not owned by user', async () => { 67 | const sdk = createMagicAdminSDK('https://example.com'); 68 | 69 | // Mock the magic token validation to return ok 70 | sdk.token.validate = jest.fn().mockResolvedValue({}); 71 | // Mock the getPublicAddress to return valid email and wallet 72 | sdk.token.getPublicAddress = jest.fn().mockReturnValue('0x610dcb8fd5cf7f544b85290889a456916fbeaba2'); 73 | 74 | await expect( 75 | sdk.utils.validateTokenOwnership( 76 | 'did:ethr:0x123', 77 | '0x610dcb8fd5cf7f544b85290889a456916fbeaba2', 78 | 'ERC721', 79 | 'https://example.com', 80 | '1', 81 | ), 82 | ).resolves.toEqual({ 83 | valid: false, 84 | error_code: 'NO_OWNERSHIP', 85 | message: 'User does not own this token.', 86 | }); 87 | }); 88 | 89 | test('Returns an error if ERC1155 token is not owned by user', async () => { 90 | const sdk = createMagicAdminSDK('https://example.com'); 91 | 92 | // Mock the magic token validation to return ok 93 | sdk.token.validate = jest.fn().mockResolvedValue({}); 94 | // Mock the getPublicAddress to return valid email and wallet 95 | sdk.token.getPublicAddress = jest.fn().mockReturnValue('0x610dcb8fd5cf7f544b85290889a456916fbeaba2'); 96 | 97 | await expect( 98 | sdk.utils.validateTokenOwnership( 99 | 'did:ethr:0x123', 100 | '0x610dcb8fd5cf7f544b85290889a456916fbeaba2', 101 | 'ERC1155', 102 | 'https://example.com', 103 | '1', 104 | ), 105 | ).resolves.toEqual({ 106 | valid: false, 107 | error_code: 'NO_OWNERSHIP', 108 | message: 'User does not own this token.', 109 | }); 110 | }); 111 | 112 | test('Returns success if ERC1155 token is owned by user', async () => { 113 | const sdk = createMagicAdminSDK('https://example.com'); 114 | 115 | // Mock the magic token validation to return ok 116 | sdk.token.validate = jest.fn().mockResolvedValue({}); 117 | // Mock the getPublicAddress to return valid email and wallet 118 | sdk.token.getPublicAddress = jest.fn().mockReturnValue('0x610dcb8fd5cf7f544b85290889a456916fbeaba2'); 119 | 120 | await expect( 121 | sdk.utils.validateTokenOwnership( 122 | 'did:ethr:0x123', 123 | '0x610dcb8fd5cf7f544b85290889a456916fbeaba2', 124 | 'ERC1155', 125 | 'https://example.com', 126 | '2', 127 | ), 128 | ).resolves.toEqual({ 129 | valid: true, 130 | error_code: '', 131 | message: '', 132 | }); 133 | }); 134 | -------------------------------------------------------------------------------- /test/spec/utils/codec.spec.ts: -------------------------------------------------------------------------------- 1 | import * as codec from '../../../src/utils/codec'; 2 | import { DECODED_DIDT, VALID_DIDT } from '../../lib/constants'; 3 | 4 | test('Decoding', async () => { 5 | const decodedString = codec.decodeValue(VALID_DIDT); 6 | 7 | expect(decodedString).toBe(DECODED_DIDT); 8 | }); 9 | 10 | test('Decoding malformed string', async () => { 11 | const decodedString = codec.decodeValue(`tRollGoat${VALID_DIDT}`); 12 | 13 | expect(decodedString).not.toBe(DECODED_DIDT); 14 | }); 15 | 16 | test('Decoding null value', async () => { 17 | const decodedString = codec.decodeValue(null); 18 | 19 | expect(decodedString).toBe(''); 20 | }); 21 | 22 | test('Decoding undefined', async () => { 23 | const decodedString = codec.decodeValue(undefined); 24 | 25 | expect(decodedString).toBe(''); 26 | }); 27 | 28 | it('should return decoded value if running in browser', () => { 29 | // Mocking the window object 30 | (global as any).window = { 31 | atob: jest.fn().mockReturnValue(DECODED_DIDT) // Mocking atob method 32 | }; 33 | 34 | jest.spyOn(codec, 'isBrowser').mockReturnValue(true); 35 | 36 | expect(codec.decodeValue(VALID_DIDT)).toBe(DECODED_DIDT); 37 | }); -------------------------------------------------------------------------------- /test/spec/utils/issuer/generateIssuerFromPublicAddress.spec.ts: -------------------------------------------------------------------------------- 1 | import { generateIssuerFromPublicAddress } from '../../../../src/utils/issuer'; 2 | 3 | test('Successfully builds issuer string from public address', async () => { 4 | const result = generateIssuerFromPublicAddress('0x1234'); 5 | expect(result).toBe('did:ethr:0x1234'); 6 | }); 7 | 8 | test('Successfully builds issuer string from public address with overrided method', async () => { 9 | const result = generateIssuerFromPublicAddress('0x1234', 'test'); 10 | expect(result).toBe('did:test:0x1234'); 11 | }); 12 | -------------------------------------------------------------------------------- /test/spec/utils/issuer/parsePublicAddressFromIssuer.spec.ts: -------------------------------------------------------------------------------- 1 | import { parsePublicAddressFromIssuer } from '../../../../src/utils/issuer'; 2 | 3 | test('Successfully parses public address from issuer string', async () => { 4 | const result = parsePublicAddressFromIssuer('did:ethr:0x1234'); 5 | expect(result).toBe('0x1234'); 6 | }); 7 | 8 | test('Returns empty string if public address fails to parse', async () => { 9 | // Notice that public address should be undefined 10 | const result = parsePublicAddressFromIssuer('did:ethr'); 11 | expect(result).toBe(''); 12 | }); 13 | -------------------------------------------------------------------------------- /test/spec/utils/parse-didt.spec.ts: -------------------------------------------------------------------------------- 1 | import { VALID_DIDT, VALID_DIDT_DECODED, INVALID_DIDT_MALFORMED_CLAIM } from '../../lib/constants'; 2 | import { createMalformedTokenError } from '../../../src/core/sdk-exceptions'; 3 | import { parseDIDToken } from '../../../src/utils/parse-didt'; 4 | 5 | test('Successfully parses DIDT', async () => { 6 | const result = parseDIDToken(VALID_DIDT); 7 | expect(result.withParsedClaim).toEqual(VALID_DIDT_DECODED); 8 | }); 9 | 10 | test('Throws error if token is malformed', async () => { 11 | const expectedError = createMalformedTokenError(); 12 | expect(() => parseDIDToken(INVALID_DIDT_MALFORMED_CLAIM)).toThrow(expectedError); 13 | }); 14 | -------------------------------------------------------------------------------- /test/spec/utils/rest/emitRequest.spec.ts: -------------------------------------------------------------------------------- 1 | import { fetch } from '../../../../src/utils/fetch'; 2 | import { API_KEY } from '../../../lib/constants'; 3 | import { get } from '../../../../src/utils/rest'; 4 | import { createServiceError } from '../../../../src/core/sdk-exceptions'; 5 | 6 | /* 7 | We test remaining code paths to the private function `emitRequest` via the 8 | public `get` function. 9 | */ 10 | 11 | const URL = 'https://example.com/hello/world'; 12 | 13 | const failWithTypeError = Promise.resolve({ 14 | json: undefined, 15 | }); 16 | 17 | const failWithBadStatus = Promise.resolve({ 18 | json: () => 19 | Promise.resolve({ 20 | status: 'qwerty', // Only 'ok' with succeed 21 | }), 22 | }); 23 | 24 | const failWithEmptyStatus = Promise.resolve({ 25 | json: () => 26 | Promise.resolve({ 27 | // No status defined will assume non-'ok' 28 | }), 29 | }); 30 | 31 | const successResEmptyData = Promise.resolve({ 32 | json: () => 33 | Promise.resolve({ 34 | status: 'ok', 35 | }), 36 | }); 37 | 38 | test('Fails with TypeError if `res.json` is undefined', async () => { 39 | // This test allow us to force `fetch` to catch. This test is primarily for 40 | // coverage purposes. This case should likely never happen. 41 | 42 | const fetchStub = jest.fn().mockImplementation(() => failWithTypeError); 43 | (fetch as any) = fetchStub; 44 | 45 | const expectedError = createServiceError({ status: 'qwerty' }); 46 | 47 | await expect(get(URL, API_KEY)).rejects.toThrow(expectedError); 48 | }); 49 | 50 | test('Fails with non-OK status in response JSON', async () => { 51 | const fetchStub = jest.fn().mockImplementation(() => failWithBadStatus); 52 | (fetch as any) = fetchStub; 53 | 54 | const expectedError = createServiceError({ status: 'qwerty' }); 55 | 56 | await expect(get(URL, API_KEY)).rejects.toThrow(expectedError); 57 | }); 58 | 59 | test('Succeeds with empty data in response JSON, returning `{}` as fallback', async () => { 60 | const fetchStub = jest.fn().mockImplementation(() => successResEmptyData); 61 | (fetch as any) = fetchStub; 62 | await expect(get(URL, API_KEY)).resolves.toEqual({}); 63 | }); 64 | 65 | test('Fails with empty status in response', async () => { 66 | const fetchStub = jest.fn().mockImplementation(() => failWithEmptyStatus); 67 | (fetch as any) = fetchStub; 68 | 69 | const expectedError = createServiceError({ status: 'qwerty' }); 70 | 71 | expect(get(URL, API_KEY)).rejects.toThrow(expectedError); 72 | }); 73 | -------------------------------------------------------------------------------- /test/spec/utils/rest/get.spec.ts: -------------------------------------------------------------------------------- 1 | import { fetch } from '../../../../src/utils/fetch'; 2 | import { API_KEY } from '../../../lib/constants'; 3 | import { get } from '../../../../src/utils/rest'; 4 | 5 | const successRes = Promise.resolve({ 6 | json: () => 7 | Promise.resolve({ 8 | data: 'hello world', 9 | status: 'ok', 10 | }), 11 | }); 12 | 13 | test('Successfully GETs to the given endpoint & stringifies query params', async () => { 14 | const fetchStub = jest.fn().mockImplementation(() => successRes); 15 | (fetch as any) = fetchStub; 16 | await expect(get('https://example.com/hello/world', API_KEY, { foo: 'hello', bar: 'world' })).resolves.toBe( 17 | 'hello world', 18 | ); 19 | 20 | const fetchArguments = fetchStub.mock.calls[0]; 21 | expect(fetchArguments).toEqual([ 22 | 'https://example.com/hello/world?foo=hello&bar=world', 23 | { 24 | method: 'GET', 25 | headers: { 'X-Magic-Secret-key': API_KEY }, 26 | }, 27 | ]); 28 | }); 29 | 30 | test('Successfully GETs to the given endpoint with no query params', async () => { 31 | const fetchStub = jest.fn().mockImplementation(() => successRes); 32 | (fetch as any) = fetchStub; 33 | 34 | await expect(get('https://example.com/hello/world', API_KEY)).resolves.toBe('hello world'); 35 | 36 | const fetchArguments = fetchStub.mock.calls[0]; 37 | expect(fetchArguments).toEqual([ 38 | 'https://example.com/hello/world', 39 | { 40 | method: 'GET', 41 | headers: { 'X-Magic-Secret-key': API_KEY }, 42 | }, 43 | ]); 44 | }); 45 | -------------------------------------------------------------------------------- /test/spec/utils/rest/post.spec.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | import { API_KEY } from '../../../lib/constants'; 3 | import { post } from '../../../../src/utils/rest'; 4 | 5 | const successRes = Promise.resolve({ 6 | json: () => 7 | Promise.resolve({ 8 | data: 'hello world', 9 | status: 'ok', 10 | }), 11 | }); 12 | 13 | test('Successfully POSTs to the given endpoint & stringifies body', async () => { 14 | const fetchStub = jest.fn().mockImplementation(() => successRes); 15 | (fetch as any) = fetchStub; 16 | 17 | await expect(post('https://example.com/hello/world', API_KEY, { public_address: '0x0123' })).resolves.toBe( 18 | 'hello world', 19 | ); 20 | 21 | const fetchArguments = fetchStub.mock.calls[0]; 22 | expect(fetchArguments).toEqual([ 23 | 'https://example.com/hello/world', 24 | { 25 | method: 'POST', 26 | headers: { 'X-Magic-Secret-key': API_KEY }, 27 | body: '{"public_address":"0x0123"}', 28 | }, 29 | ]); 30 | }); 31 | -------------------------------------------------------------------------------- /test/spec/utils/type-guards/isDIDTClaim.spec.ts: -------------------------------------------------------------------------------- 1 | import { isDIDTClaim } from '../../../../src/utils/type-guards'; 2 | 3 | test('Returns false given `undefined`', async () => { 4 | expect(isDIDTClaim(undefined)).toBe(false); 5 | }); 6 | 7 | test('Returns false given `null`', async () => { 8 | expect(isDIDTClaim(null)).toBe(false); 9 | }); 10 | 11 | test('Returns false given without `Claim.iat`', async () => { 12 | expect(isDIDTClaim({ ext: 123, iss: 'asdf', sub: 'asdf', aud: 'asdf', nbf: 123, tid: 'asdf', add: '0x0123' })).toBe( 13 | false, 14 | ); 15 | }); 16 | 17 | test('Returns false given without `Claim.ext`', async () => { 18 | expect(isDIDTClaim({ iat: 123, iss: 'asdf', sub: 'asdf', aud: 'asdf', nbf: 123, tid: 'asdf', add: '0x0123' })).toBe( 19 | false, 20 | ); 21 | }); 22 | 23 | test('Returns false given without `Claim.iss`', async () => { 24 | expect(isDIDTClaim({ iat: 123, ext: 123, sub: 'asdf', aud: 'asdf', nbf: 123, tid: 'asdf', add: '0x0123' })).toBe( 25 | false, 26 | ); 27 | }); 28 | 29 | test('Returns false given without `Claim.sub`', async () => { 30 | expect(isDIDTClaim({ iat: 123, ext: 123, iss: 'asdf', aud: 'asdf', nbf: 123, tid: 'asdf', add: '0x0123' })).toBe( 31 | false, 32 | ); 33 | }); 34 | 35 | test('Returns false given without `Claim.aud`', async () => { 36 | expect(isDIDTClaim({ iat: 123, ext: 123, iss: 'asdf', sub: 'asdf', nbf: 123, tid: 'asdf', add: '0x0123' })).toBe( 37 | false, 38 | ); 39 | }); 40 | 41 | test('Returns false given without `Claim.nbf`', async () => { 42 | expect(isDIDTClaim({ iat: 123, ext: 123, iss: 'asdf', sub: 'asdf', aud: 'asdf', tid: 'asdf', add: '0x0123' })).toBe( 43 | false, 44 | ); 45 | }); 46 | 47 | test('Returns false given without `Claim.tid`', async () => { 48 | expect(isDIDTClaim({ iat: 123, ext: 123, iss: 'asdf', sub: 'asdf', aud: 'asdf', nbf: 123, add: '0x0123' })).toBe( 49 | false, 50 | ); 51 | }); 52 | 53 | test('Returns false given without `Claim.add`', async () => { 54 | expect(isDIDTClaim({ iat: 123, ext: 123, iss: 'asdf', sub: 'asdf', aud: 'asdf', nbf: 123, tid: 'asdf' })).toBe(false); 55 | }); 56 | 57 | test('Returns true given with all required properties', async () => { 58 | expect( 59 | isDIDTClaim({ iat: 123, ext: 123, iss: 'asdf', sub: 'asdf', aud: 'asdf', nbf: 123, tid: 'asdf', add: '0x0123' }), 60 | ).toBe(true); 61 | }); 62 | -------------------------------------------------------------------------------- /test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../config/tsconfig.test.json", 3 | "compilerOptions": { 4 | "target": "es5" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./config/tsconfig.base.json", 3 | "compilerOptions": { 4 | "target": "es5" 5 | } 6 | } 7 | --------------------------------------------------------------------------------