├── .all-contributorsrc ├── .babelrc ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── codeql.yml │ ├── comment-issue.yml │ └── lint.yml ├── .gitignore ├── .versions ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── community-services ├── angellist.js ├── apple.js ├── betapass.js ├── discord.js ├── dropbox.js ├── edmodo.js ├── instagram.js ├── line.js ├── linkedin.js ├── mailru.js ├── office365.js ├── ok.js ├── qq.js ├── seznam.js ├── slack.js ├── soundcloud.js ├── spotify.js ├── twitch.js ├── venmo.js ├── vk.js ├── web3.js └── wechat.js ├── core-services ├── facebook.js ├── github.js ├── google.js ├── meetup.js ├── meteor_developer.js ├── twitter.js └── weibo.js ├── example ├── .meteor │ ├── .finished-upgraders │ ├── .gitignore │ ├── .id │ ├── cordova-plugins │ ├── packages │ ├── platforms │ ├── release │ └── versions ├── README.md ├── example.html └── example.js ├── link_accounts_client.js ├── link_accounts_server.js ├── package-lock.json ├── package.js └── package.json /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "projectName": "template-package", 3 | "projectOwner": "Meteor-Community-Packages", 4 | "repoType": "github", 5 | "repoHost": "https://github.com", 6 | "files": [ 7 | "README.md" 8 | ], 9 | "imageSize": 100, 10 | "commit": true, 11 | "commitConvention": "eslint", 12 | "contributors": [ 13 | { 14 | "login": "yubozhao", 15 | "name": "Yu Bozhao", 16 | "profile": "https://github.com/yubozhao", 17 | "avatar_url": "https://avatars1.githubusercontent.com/u/670949?s=460&v=4", 18 | "contributions": [ 19 | "code", 20 | "doc", 21 | "maintenance" 22 | ] 23 | }, 24 | { 25 | "login": "StorytellerCZ", 26 | "name": "Jan Dvorak", 27 | "avatar_url": "https://avatars2.githubusercontent.com/u/1715235?v=4", 28 | "profile": "https://github.com/StorytellerCZ", 29 | "contributions": [ 30 | "code", 31 | "doc", 32 | "maintenance" 33 | ] 34 | }, 35 | { 36 | "login": "jankapunkt", 37 | "name": "Jan Küster", 38 | "avatar_url": "https://avatars.githubusercontent.com/u/1135285?v=4", 39 | "profile": "https://jankuester.com/", 40 | "contributions": [ 41 | "maintenance" 42 | ] 43 | } 44 | ], 45 | "contributorsPerLine": 7 46 | } 47 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "@babel/preset-env", 5 | { 6 | "shippedProposals": true, 7 | "useBuiltIns": "usage", 8 | "corejs": "3" 9 | } 10 | ] 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [StorytellerCZ] 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | 12 | A clear and concise description of what the bug is. 13 | 14 | **To Reproduce** 15 | 16 | Steps to reproduce the behavior: 17 | 1. Go to '...' 18 | 2. Click on '....' 19 | 3. Scroll down to '....' 20 | 4. See error 21 | 22 | **Expected behavior** 23 | 24 | A clear and concise description of what you expected to happen. 25 | 26 | **Screenshots** 27 | 28 | If applicable, add screenshots to help explain your problem. 29 | 30 | **Versions (please complete the following information):** 31 | - Meteor version: [e.g. 1.8.2] 32 | - Browser: [e.g. firefox, chrome, safari] 33 | - Version: [e.g. 1.0.0] 34 | 35 | **Additional context** 36 | 37 | Add any other context about the problem here. 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | 12 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 13 | 14 | **Describe the solution you'd like** 15 | 16 | A clear and concise description of what you want to happen. 17 | 18 | **Describe alternatives you've considered** 19 | 20 | A clear and concise description of any alternative solutions or features you've considered. 21 | 22 | **Additional context** 23 | 24 | Add any other context or screenshots about the feature request here. 25 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 10 | 11 | 14 | ## What 15 | 16 | 17 | ## Why 18 | 19 | 20 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "CodeQL" 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | schedule: 9 | - cron: "15 15 * * 2" 10 | 11 | jobs: 12 | analyze: 13 | name: Analyze 14 | runs-on: ubuntu-latest 15 | permissions: 16 | actions: read 17 | contents: read 18 | security-events: write 19 | 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | language: [ javascript ] 24 | 25 | steps: 26 | - name: Checkout 27 | uses: actions/checkout@v3 28 | 29 | - name: Initialize CodeQL 30 | uses: github/codeql-action/init@v2 31 | with: 32 | languages: ${{ matrix.language }} 33 | queries: +security-and-quality 34 | 35 | - name: Autobuild 36 | uses: github/codeql-action/autobuild@v2 37 | 38 | - name: Perform CodeQL Analysis 39 | uses: github/codeql-action/analyze@v2 40 | with: 41 | category: "/language:${{ matrix.language }}" 42 | -------------------------------------------------------------------------------- /.github/workflows/comment-issue.yml: -------------------------------------------------------------------------------- 1 | name: Add immediate comment on new issues 2 | 3 | on: 4 | issues: 5 | types: [opened] 6 | 7 | jobs: 8 | createComment: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Create Comment 12 | uses: peter-evans/create-or-update-comment@v1.4.2 13 | with: 14 | issue-number: ${{ github.event.issue.number }} 15 | body: | 16 | Thank you for submitting this issue! 17 | 18 | We, the Members of Meteor Community Packages take every issue seriously. 19 | Our goal is to provide long-term lifecycles for packages and keep up 20 | with the newest changes in Meteor and the overall NodeJs/JavaScript ecosystem. 21 | 22 | However, we contribute to these packages mostly in our free time. 23 | Therefore, we can't guarantee you issues to be solved within certain time. 24 | 25 | If you think this issue is trivial to solve, don't hesitate to submit 26 | a pull request, too! We will accompany you in the process with reviews and hints 27 | on how to get development set up. 28 | 29 | Please also consider sponsoring the maintainers of the package. 30 | If you don't know who is currently maintaining this package, just leave a comment 31 | and we'll let you know 32 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: "Lint test" 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | app: 11 | name: "Lint checking app" 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-node@v4 16 | with: 17 | node-version: '20' 18 | - name: Install dependencies 19 | run: npm ci 20 | - name: Run lint 21 | run: npm run lint-test-ci 22 | 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | # IntelliJ project files 3 | .idea 4 | *.iml 5 | out 6 | gen 7 | -------------------------------------------------------------------------------- /.versions: -------------------------------------------------------------------------------- 1 | accounts-base@3.0.1 2 | accounts-oauth@1.4.5 3 | allow-deny@2.0.0 4 | babel-compiler@7.11.0 5 | babel-runtime@1.5.2 6 | base64@1.0.13 7 | binary-heap@1.0.12 8 | boilerplate-generator@2.0.0 9 | bozhao:link-accounts@3.0.1 10 | callback-hook@1.6.0 11 | check@1.4.2 12 | core-runtime@1.0.0 13 | ddp@1.4.2 14 | ddp-client@3.0.1 15 | ddp-common@1.4.4 16 | ddp-rate-limiter@1.2.2 17 | ddp-server@3.0.1 18 | diff-sequence@1.1.3 19 | dynamic-import@0.7.4 20 | ecmascript@0.16.9 21 | ecmascript-runtime@0.8.2 22 | ecmascript-runtime-client@0.12.2 23 | ecmascript-runtime-server@0.11.1 24 | ejson@1.1.4 25 | facts-base@1.0.2 26 | fetch@0.1.5 27 | geojson-utils@1.0.12 28 | id-map@1.2.0 29 | inter-process-messaging@0.1.2 30 | localstorage@1.2.1 31 | logging@1.3.5 32 | meteor@2.0.1 33 | minimongo@2.0.1 34 | modern-browsers@0.1.11 35 | modules@0.20.1 36 | modules-runtime@0.13.2 37 | mongo@2.0.1 38 | mongo-decimal@0.1.4-beta300.7 39 | mongo-dev-server@1.1.1 40 | mongo-id@1.0.9 41 | npm-mongo@4.17.4 42 | oauth@3.0.0 43 | ordered-dict@1.2.0 44 | promise@1.0.0 45 | random@1.2.2 46 | rate-limit@1.1.2 47 | react-fast-refresh@0.2.9 48 | reactive-var@1.0.13 49 | reload@1.3.2 50 | retry@1.1.1 51 | routepolicy@1.1.2 52 | service-configuration@1.3.5 53 | socket-stream-client@0.5.3 54 | tracker@1.3.4 55 | typescript@5.4.3 56 | underscore@1.6.4 57 | url@1.3.3 58 | webapp@2.0.1 59 | webapp-hashing@1.1.2 60 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v. 3.0.1 4 | * Fix a wrong if statement in the Seznam connector 5 | 6 | ## v. 3.0.0 7 | * Removed deprecated providers 8 | * Minimum support version is Meteor 3.0 9 | 10 | ## v. 2.9.0 11 | * Added `storyteller:accounts-seznam` 12 | 13 | ## v. 2.8.0 14 | * More async calls have been added through the codebase 15 | * Minimum Meteor version is `2.9.11` 16 | * Deprecated `lichthagel:accounts-discord` which will be removed in v3 17 | * Betapass client preview added 18 | * Supports Meteor 3 `alpha-15` 19 | 20 | ## v. 2.7.1-alpha.1 - 23.8.2023 21 | * Added build target for Meteor 3 `alpha.11` 22 | * Updated npm dev dependencies 23 | 24 | ## v. 2.7.0 - 13.8.2023 25 | * Added `storyteller:accounts-discord` as a new version for Discord connector 26 | * Added new build target for Meteor v 2.9.1 27 | * Add re-tries for popup closures in `tryLinkAfterPopupClosed` to mirror Meteor login behavior and fix issues when `credentialSecret` is not set fast enough 28 | 29 | ## v. 2.6.1 - 11.5.2022 30 | * Fixes for web3 login to keep up with change in `freedombase:web3-login` 31 | 32 | ## v. 2.6.0 - 10.5.2022 33 | * Added `freedombase:web3-login` 34 | * Slight code re-format for better readability 35 | 36 | ## v. 2.5.0 - 9.5.2022 37 | * Make hooks stoppable 38 | * Fix commit script 39 | * Added Apple provider for `quave:accounts-apple` or `bigowl:accounts-apple` package 40 | 41 | ## v. 2.4.1 - 10.11.2021 42 | * Upgrade eslint-parser 43 | * Fix wrong uppercase in the word `linkedIn` for `pauli:linkedin-oauth` 44 | 45 | ## v. 2.4.0 - 24.6.2021 46 | 47 | * Updated dependencies 48 | * Compatibility update for Meteor 2.3 49 | 50 | ## v. 2.3.2 - 8.2.2021 51 | * Fix bugs in MS Office connector 52 | 53 | ## v. 2.3.1 - 8.2.2021 54 | * Fix a detection bug in MS Office connector 55 | 56 | ## v. 2.3.0 - 29.1.2021 57 | * Added extra check on linking accounts and made connection errors provide a bit more information 58 | * Updated development dependencies 59 | * Add support for MS Office 365 logins via `lindoelio:accounts-office365` and `ermlab:accounts-office365` 60 | 61 | ## v. 2.2.1 - 6.9.2020 62 | * Fix Meteor Developer Accounts link 63 | 64 | ## v. 2.2.0 - 2.9.2020 65 | * Updated example to Meteor 1.10.2 66 | * Bumped minimum required version of Meteor to 1.4.3 67 | * Add missing imports 68 | * Fix code to adhere to StandardJS 69 | * Add hooks (`Accounts.onLink`, `Accounts.beforeLink`, `Accounts.onUnlink`) 70 | 71 | ## v. 2.1.1 - 28.4.2020 72 | * Fix error in Discord connector 73 | * Format changelog 74 | * Added GitHub settings & legal stuff 75 | 76 | ## v. 2.1.0 - 18.09.2019 77 | * `jonperl:linkedin` has been deprecated in favor of `pauli:linkedin-oauth` in order to keep up with changes in LinkedIn API. You will see a depracetion warning in your app for this. The old package will work, but will be removed in future releases. 78 | 79 | ## v. 2.0.2 80 | * Fix unlinked accounts method 81 | * Fix missing imports 82 | * Fix minimum required Meteor version to v 1.3 83 | 84 | ## v. 2.0.0 85 | * Modernize package to use with `ecmascript` package 86 | * Drop `underscore` 87 | * LINE support 88 | 89 | ## v. 1.2.0 90 | * unlink accounts 91 | * linkedin support 92 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Meteor Community Code of Conduct 2 | 3 | ### Community 4 | 5 | We want to build a productive, happy and agile community that welcomes new ideas, constantly looks for areas to improve, and fosters collaboration. 6 | 7 | The project gains strength from a diversity of backgrounds and perspectives in our contributor community, and we actively seek participation from those who enhance it. This code of conduct exists to lay some ground rules that ensure we can collaborate and communicate effectively, despite our diversity. The code applies equally to founders, team members and those seeking help and guidance. 8 | 9 | ### Using This Code 10 | 11 | This isn’t an exhaustive list of things that you can’t do. Rather, it’s a guide for participation in the community that outlines how each of us can work to keep Meteor a positive, successful, and growing project. 12 | 13 | This code of conduct applies to all spaces managed by the Meteor Community project. This includes GitHub issues, and any other forums created by the team which the community uses for communication. We expect it to be honored by everyone who represents or participates in the project, whether officially or informally. 14 | 15 | #### When Something Happens 16 | 17 | If you see a Code of Conduct violation, follow these steps: 18 | 19 | * Let the person know that what they did is not appropriate and ask them to stop and/or edit their message(s). 20 | * That person should immediately stop the behavior and correct the issue. 21 | * If this doesn’t happen, or if you’re uncomfortable speaking up, contact admins. 22 | * As soon as available, an admin will join, identify themselves, and take further action (see below), starting with a warning, then temporary deactivation, then long-term deactivation. 23 | 24 | When reporting, please include any relevant details, links, screenshots, context, or other information that may be used to better understand and resolve the situation. 25 | 26 | The Admin team will prioritize the well-being and comfort of the recipients of the violation over the comfort of the violator. 27 | 28 | If you believe someone is violating the code of conduct, please report it to any member of the governing team. 29 | 30 | ### We Strive To: 31 | 32 | - **Be open, patient, and welcoming** 33 | 34 | Members of this community are open to collaboration, whether it's on PRs, issues, or problems. We're receptive to constructive comment and criticism, as we value what the experiences and skill sets of contributors bring to the project. We're accepting of all who wish to get involved, and find ways for anyone to participate in a way that best matches their strengths. 35 | 36 | - **Be considerate** 37 | 38 | We are considerate of our peers: other Meteor users and contributors. We’re thoughtful when addressing others’ efforts, keeping in mind that work is often undertaken for the benefit of the community. We also value others’ time and appreciate that not every issue or comment will be responded to immediately. We strive to be mindful in our communications, whether in person or online, and we're tactful when approaching views that are different from our own. 39 | 40 | - **Be respectful** 41 | 42 | As a community of professionals, we are professional in our handling of disagreements, and don’t allow frustration to turn into a personal attack. We work together to resolve conflict, assume good intentions and do our best to act in an empathic fashion. 43 | 44 | We do not tolerate harassment or exclusionary behavior. This includes, but is not limited to: 45 | - Violent threats or language directed against another person. 46 | - Discriminatory jokes and language. 47 | - Posting sexually explicit or sexualized content. 48 | - Posting content depicting or encouraging violence. 49 | - Posting (or threatening to post) other people's personally identifying information ("doxing"). 50 | - Personal insults, especially those using racist or sexist terms. 51 | - Unwelcome sexual attention. 52 | - Advocating for, or encouraging, any of the above behavior. 53 | - Repeated harassment of others. In general, if someone asks you to stop, then stop. 54 | 55 | - **Take responsibility for our words and our actions** 56 | 57 | We can all make mistakes; when we do, we take responsibility for them. If someone has been harmed or offended, we listen carefully and respectfully. We are also considerate of others’ attempts to amend their mistakes. 58 | 59 | - **Be collaborative** 60 | 61 | The work we produce is (and is part of) an ecosystem containing several parallel efforts working towards a similar goal. Collaboration between teams and individuals that each have their own goal and vision is essential to reduce redundancy and improve the quality of our work. 62 | 63 | Internally and externally, we celebrate good collaboration. Wherever possible, we work closely with upstream projects and others in the free software community to coordinate our efforts. We prefer to work transparently and involve interested parties as early as possible. 64 | 65 | - **Ask for help when in doubt** 66 | 67 | Nobody is expected to be perfect in this community. Asking questions early avoids many problems later, so questions are encouraged, though they may be directed to the appropriate forum. Those who are asked should be responsive and helpful. 68 | 69 | - **Take initiative** 70 | 71 | We encourage new participants to feel empowered to lead, to take action, and to experiment when they feel innovation could improve the project. If we have an idea for a new tool, or how an existing tool can be improved, we speak up and take ownership of that work when possible. 72 | 73 | ### Attribution 74 | 75 | This Code of Conduct was inspired by [Meteor CoC](https://github.com/meteor/meteor/blob/devel/CODE_OF_CONDUCT.md). 76 | 77 | Sections of this Code of Conduct were inspired in by the following Codes from other open source projects and resources we admire: 78 | 79 | - [The Contributor Covenant](http://contributor-covenant.org/version/1/4/) 80 | - [Python](https://www.python.org/psf/codeofconduct/) 81 | - [Ubuntu](http://www.ubuntu.com/about/about-ubuntu/conduct) 82 | - [Django](https://www.djangoproject.com/conduct/) 83 | 84 | *This Code of Conduct is licensed under the [Creative Commons Attribution-ShareAlike 4.0 International](https://creativecommons.org/licenses/by-sa/4.0/) license. This Code was last updated on March 12, 2019.* 85 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute to this repository 2 | [Read more about submitting a contribution.](https://opensource.guide/how-to-contribute/#how-to-submit-a-contribution) 3 | 4 | ## Getting started 5 | 6 | ### Clone the repository to your account 7 | 8 | ### Setup 9 | * Run `meteor npm i` to get npm dependencies. 10 | 11 | ### Make changes & test 12 | * Make your changes. 13 | * Run tests via `npm run test` & test your implementation. 14 | * If everything is green commit and push. 15 | * Submit pull request 16 | 17 | [Read more about how to properly open a pull request.](https://opensource.guide/how-to-contribute/#opening-a-pull-request) 18 | 19 | ## Miscellaneous stuff beyond your contribution 20 | 21 | ## Add contributor mention 22 | If this is your first time contributing to this repository or your contribution is of different type from your previous contribution, don't forget to use [All Contributors Bot](https://allcontributors.org/docs/en/bot/usage) to make your contribution listed. 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2015-present Meteor Community 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 | # Meteor Link Accounts 2 | 3 | [![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active) 4 | 5 | A [Meteor package](https://atmospherejs.com/bozhao/link-accounts) designed to links social network accounts without any hassles. 6 | 7 | ## Goals 8 | * Link additional social network accounts. 9 | * Don't modify any Meteor core packages. 10 | * Don't force users to add additional Meteor packages that they are not going to use. 11 | 12 | ## Install 13 | Install in Meteor with: 14 | 15 | ```bash 16 | meteor add bozhao:link-accounts 17 | ``` 18 | 19 | ## Usage 20 | ### Client side 21 | #### Meteor.linkWith[ServiceName](options, callback) 22 | You will call this on the page where you allow your users to connect to other services. This method will be triggered after they click the appropriate connect button. 23 | 24 | `options` is expecting configuration object. Most often that is going to be: `{ loginStyle: 'popup' }` 25 | 26 | ##### `freedombase:web3-login` 27 | The `options` object accepts `linkMessage` key where you can set message for signature. 28 | 29 | ### Server side 30 | #### Accounts.unlinkService(userId, serviceName) 31 | Given the `userId` and the name of the service (`serviceName`) as it is named in the user document (most often lower case name of the service). 32 | 33 | ### Hooks 34 | There are 3 hooks available to you on the server at various time during the link process. All are triggered on the server side. 35 | #### Accounts.beforeLink 36 | Called before user account is linked with service. The hook will receive object with the following parameters: 37 | * `type` - service name 38 | * `serviceData` - data received from the service provider 39 | * `user` - current user object 40 | * `serviceOptions` - options that were used to call the service 41 | 42 | If you return false the linking will be interrupted. 43 | 44 | #### Accounts.onLink 45 | Called after user has been linked with a service. The hook will receive object with the following parameters: 46 | * `type` - service name 47 | * `serviceData` - data received from the service provider 48 | * `user` - updated user object 49 | * `serviceOptions` - options that were used to call the service 50 | 51 | #### Accounts.onUnlink 52 | Called after user is unlinking the service. The hook will receive object with the following parameters: 53 | * `type` - service name that was unlinked 54 | * `user` - user object before unlinking 55 | 56 | ## Design notes: 57 | 1. Piggyback on existing Meteor oauth login system. Use login handler. 58 | 59 | 2. We do not allow link different account from same service for now. For example, you 60 | could not link with 2 different Github accounts. 61 | 62 | 3. Save the linked service info on user.services, instead of creating new field 63 | on user object. This allows user logins the application from linked services. 64 | 65 | 4. Don't create a temporary user account and then merge it. 66 | 67 | ## Support Accounts Package 68 | 69 | ### Official packages 70 | * accounts-meteor-developer 71 | * accounts-github 72 | * accounts-facebook 73 | * accounts-google 74 | * accounts-twitter 75 | * accounts-meetup 76 | * accounts-weibo 77 | 78 | ### Community packages 79 | * btafel:accounts-facebook-cordova 80 | * bozhao:accounts-instagram 81 | * mrt:accounts-vk 82 | * mikepol:accounts-ok 83 | * mikepol:accounts-mailru 84 | * pauli:linkedin-oauth 85 | * garbolino:accounts-soundcloud 86 | * alexbeauchemin:accounts-twitch 87 | * nicolaiwadstrom:meteor-angellist 88 | * acemtp:meteor-slack 89 | * xinranxiao:meteor-spotify 90 | * gcampax:accounts-dropbox 91 | * pcooney10:accounts-venmo 92 | * leonzhang1109:accounts-wechat 93 | * leonzhang1109:accounts-qq 94 | * storyteller:accounts-line 95 | * lindoelio:accounts-office365 / ermlab:accounts-office365 96 | * quave:accounts-apple / bigowl:accounts-apple 97 | * freedombase:web3-login 98 | * storyteller:accounts-discord 99 | 100 | ## License 101 | MIT 102 | 103 | ## Contributors 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 |
Yu Bozhao
Yu Bozhao

💻 📖 🚧
Jan Dvorak
Jan Dvorak

💻 📖 🚧
Jan Küster
Jan Küster

🚧
117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /community-services/angellist.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | 4 | Meteor.linkWithAngelList = function (options, callback) { 5 | if (!Meteor.userId()) { 6 | throw new Meteor.Error(402, 'Please login to an existing account before link.') 7 | } 8 | if (!Package['nicolaiwadstrom:meteor-angellist'] || !Package['nicolaiwadstrom:meteor-accounts-angellist']) { 9 | throw new Meteor.Error(403, 'Please include nicolaiwadstrom:meteor-angellist package') 10 | } 11 | 12 | if (!callback && typeof options === 'function') { 13 | callback = options 14 | options = null 15 | } 16 | 17 | const credentialRequestCompleteCallback = Accounts.oauth.linkCredentialRequestCompleteHandler(callback) 18 | Package['nicolaiwadstrom:meteor-accounts-angellist'].AngelList.requestCredential( 19 | options, 20 | credentialRequestCompleteCallback 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /community-services/apple.js: -------------------------------------------------------------------------------- 1 | import { Accounts } from 'meteor/accounts-base' 2 | import { Meteor } from 'meteor/meteor' 3 | 4 | Meteor.linkWithApple = function (options, callback, nativeCallback) { 5 | if (!Meteor.userId()) { 6 | throw new Meteor.Error(402, 'Please login to an existing account before link.') 7 | } 8 | const quavePackage = Package['quave:accounts-apple'] 9 | const bigowlPackage = Package['bigowl:accounts-apple'] 10 | if (!quavePackage && !bigowlPackage) { 11 | throw new Meteor.Error(403, 'Please include quave:accounts-apple or bigowl:accounts-apple package') 12 | } 13 | 14 | if (!callback && typeof options === 'function') { 15 | callback = options 16 | options = null 17 | } 18 | 19 | const credentialRequestCompleteCallback = Accounts.oauth.linkCredentialRequestCompleteHandler(callback) 20 | const nativeCredentialRequestCompleteCallback = Accounts.oauth.linkCredentialRequestCompleteHandler(nativeCallback || callback) 21 | 22 | quavePackage 23 | ? Package['quave:apple-oauth'].Apple.requestCredential( 24 | options, 25 | nativeCredentialRequestCompleteCallback, 26 | credentialRequestCompleteCallback 27 | ) 28 | : bigowlPackage && Package['bigowl:apple-oauth'].Apple.requestCredential( 29 | options, 30 | credentialRequestCompleteCallback 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /community-services/betapass.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | 4 | Meteor.linkWithBetapass = function (options, callback) { 5 | if (!Meteor.userId()) { 6 | throw new Meteor.Error( 7 | 402, 8 | 'Please login to an existing account before link.' 9 | ) 10 | } 11 | if (!Package['storyteller:accounts-betapass']) { 12 | throw new Meteor.Error( 13 | 403, 14 | 'Please include storyteller:accounts-betapass package' 15 | ) 16 | } 17 | 18 | if (!callback && typeof options === 'function') { 19 | callback = options 20 | options = null 21 | } 22 | 23 | const credentialRequestCompleteCallback = 24 | Accounts.oauth.linkCredentialRequestCompleteHandler(callback) 25 | if (Package['storyteller:betapass-oauth']) { 26 | Package['storyteller:betapass-oauth'].Betapass.requestCredential( 27 | options, 28 | credentialRequestCompleteCallback 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /community-services/discord.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | 4 | Meteor.linkWithDiscord = function (options, callback) { 5 | if (!Meteor.userId()) { 6 | throw new Meteor.Error(402, 'Please login to an existing account before link.') 7 | } 8 | if (!Package['storyteller:accounts-discord']) { 9 | throw new Meteor.Error(403, 'Please include storyteller:accounts-discord package') 10 | } 11 | 12 | if (!callback && typeof options === 'function') { 13 | callback = options 14 | options = null 15 | } 16 | 17 | const credentialRequestCompleteCallback = Accounts.oauth.linkCredentialRequestCompleteHandler(callback) 18 | 19 | if (Package['storyteller:discord-oauth']) { 20 | Package['storyteller:discord-oauth'].Discord.requestCredential(options, credentialRequestCompleteCallback) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /community-services/dropbox.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | 4 | Meteor.linkWithDropbox = function (options, callback) { 5 | if (!Meteor.userId()) { 6 | throw new Meteor.Error(402, 'Please login to an existing account before link.') 7 | } 8 | if (!Package['gcampax:dropbox-oauth']) { 9 | throw new Meteor.Error(403, 'Please include gcampax:dropbox-oauth package') 10 | } 11 | 12 | if (!callback && typeof options === 'function') { 13 | callback = options 14 | options = null 15 | } 16 | 17 | const credentialRequestCompleteCallback = Accounts.oauth.linkCredentialRequestCompleteHandler(callback) 18 | Package['gcampax:dropbox-oauth'].DropboxOAuth.requestCredential(options, credentialRequestCompleteCallback) 19 | } 20 | -------------------------------------------------------------------------------- /community-services/edmodo.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | 4 | Meteor.linkWithEdmodo = function (options, callback) { 5 | if (!Meteor.userId()) { 6 | throw new Meteor.Error(402, 'Please login to an existing account before link.') 7 | } 8 | if (!Package['merlin:accounts-edmodo'] && !Package['merlin:edmodo']) { 9 | throw new Meteor.Error(403, 'Please include merlin:accounts-edmodo and merlin:edmodo package') 10 | } 11 | if (!callback && typeof options === 'function') { 12 | callback = options 13 | options = null 14 | } 15 | 16 | const credentialRequestCompleteCallback = Accounts.oauth.linkCredentialRequestCompleteHandler(callback) 17 | Package['merlin:accounts-edmodo'].Edmodo.requestCredential(options, credentialRequestCompleteCallback) 18 | } 19 | -------------------------------------------------------------------------------- /community-services/instagram.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | 4 | Meteor.linkWithInstagram = function (options, callback) { 5 | if (!Meteor.userId()) { 6 | throw new Meteor.Error(402, 'Please login to an existing account before link.') 7 | } 8 | if (!Package['bozhao:accounts-instagram']) { 9 | throw new Meteor.Error(403, 'Please include bozhao:accounts-instagram package') 10 | } 11 | 12 | if (!callback && typeof options === 'function') { 13 | callback = options 14 | options = null 15 | } 16 | 17 | const credentialRequestCompleteCallback = Accounts.oauth.linkCredentialRequestCompleteHandler(callback) 18 | Package['bozhao:accounts-instagram'].Instagram.requestCredential(options, credentialRequestCompleteCallback) 19 | } 20 | -------------------------------------------------------------------------------- /community-services/line.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | 4 | Meteor.linkWithLine = function (options, callback) { 5 | if (!Meteor.userId()) { 6 | throw new Meteor.Error(402, 'Please login to an existing account before link.') 7 | } 8 | if (!Package['storyteller:accounts-line']) { 9 | throw new Meteor.Error(403, 'Please include storyteller:accounts-line package.') 10 | } 11 | 12 | if (!callback && typeof options === 'function') { 13 | callback = options 14 | options = null 15 | } 16 | 17 | const credentialRequestCompleteCallback = Accounts.oauth.linkCredentialRequestCompleteHandler(callback) 18 | Package['storyteller:accounts-line'].Line.requestCredential(options, credentialRequestCompleteCallback) 19 | } 20 | -------------------------------------------------------------------------------- /community-services/linkedin.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | 4 | Meteor.linkWithLinkedIn = function (options, callback) { 5 | if (!Meteor.userId()) { 6 | throw new Meteor.Error(402, 'Please login to an existing account before link.') 7 | } 8 | 9 | if (!Package['pauli:linkedin-oauth']) { 10 | throw new Meteor.Error(403, 'Please include pauli:linkedin-oauth package') 11 | } 12 | 13 | if (!callback && typeof options === 'function') { 14 | callback = options 15 | options = null 16 | } 17 | 18 | const credentialRequestCompleteCallback = Accounts.oauth.linkCredentialRequestCompleteHandler(callback) 19 | if (Package['pauli:linkedin-oauth']) { 20 | Package['pauli:linkedin-oauth'].Linkedin.requestCredential(options, credentialRequestCompleteCallback) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /community-services/mailru.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | 4 | Meteor.linkWithMailru = function (options, callback) { 5 | if (!Meteor.userId()) { 6 | throw new Meteor.Error(402, 'Please login to an existing account before link.') 7 | } 8 | if (!Package['mikepol:accounts-mailru']) { 9 | throw new Meteor.Error(403, 'Please include mikepol:accounts-mailru package') 10 | } 11 | 12 | if (!callback && typeof options === 'function') { 13 | callback = options 14 | options = null 15 | } 16 | 17 | const credentialRequestCompleteCallback = Accounts.oauth.linkCredentialRequestCompleteHandler(callback) 18 | Package['mikepol:accounts-mailru'].Mailru.requestCredential(options, credentialRequestCompleteCallback) 19 | } 20 | -------------------------------------------------------------------------------- /community-services/office365.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | 4 | Meteor.linkWithOffice = function (options, callback) { 5 | if (!Meteor.userId()) { 6 | throw new Meteor.Error(402, 'Please login to an existing account before link.') 7 | } 8 | if (!Package['lindoelio:accounts-office365'] && !Package['ermlab:accounts-office365']) { 9 | throw new Meteor.Error(403, 'Please include either lindoelio:accounts-office365 package or ermlab:accounts-office365 package.') 10 | } 11 | 12 | if (!callback && typeof options === 'function') { 13 | callback = options 14 | options = null 15 | } 16 | 17 | const credentialRequestCompleteCallback = Accounts.oauth.linkCredentialRequestCompleteHandler(callback) 18 | if (Package['lindoelio:accounts-office365']) { 19 | Package['lindoelio:office365-oauth'].Office365.requestCredential(options, credentialRequestCompleteCallback) 20 | } else if (Package['ermlab:accounts-office365']) { 21 | Package['ermlab:office365-oauth'].Office365.requestCredential(options, credentialRequestCompleteCallback) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /community-services/ok.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | 4 | Meteor.linkWithOk = function (options, callback) { 5 | if (!Meteor.userId()) { 6 | throw new Meteor.Error(402, 'Please login to an existing account before link.') 7 | } 8 | if (!Package['mikepol:accounts-ok']) { 9 | throw new Meteor.Error(403, 'Please include mikepol:accounts-ok package') 10 | } 11 | 12 | if (!callback && typeof options === 'function') { 13 | callback = options 14 | options = null 15 | } 16 | 17 | const credentialRequestCompleteCallback = Accounts.oauth.linkCredentialRequestCompleteHandler(callback) 18 | Package['mikepol:accounts-ok'].OK.requestCredential(options, credentialRequestCompleteCallback) 19 | } 20 | -------------------------------------------------------------------------------- /community-services/qq.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | 4 | Meteor.linkWithQq = function (options, callback) { 5 | if (!Meteor.userId()) { 6 | throw new Meteor.Error(402, 'Please login to an existing account before link.') 7 | } 8 | if (!Package['leonzhang1109:accounts-qq']) { 9 | throw new Meteor.Error(403, 'Please include leonzhang1109:accounts-qq package') 10 | } 11 | 12 | if (!callback && typeof options === 'function') { 13 | callback = options 14 | options = null 15 | } 16 | 17 | const credentialRequestCompleteCallback = Accounts.oauth.linkCredentialRequestCompleteHandler(callback) 18 | Package['leonzhang1109:accounts-qq'].Qq.requestCredential(options, credentialRequestCompleteCallback) 19 | } 20 | -------------------------------------------------------------------------------- /community-services/seznam.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | 4 | Meteor.linkWithSeznam = function (options, callback) { 5 | if (!Meteor.userId()) { 6 | throw new Meteor.Error(402, 'Please login to an existing account before link.') 7 | } 8 | if (!Package['storyteller:accounts-seznam']) { 9 | throw new Meteor.Error(403, 'Please include storyteller:accounts-seznam package') 10 | } 11 | 12 | if (!callback && typeof options === 'function') { 13 | callback = options 14 | options = null 15 | } 16 | 17 | const credentialRequestCompleteCallback = Accounts.oauth.linkCredentialRequestCompleteHandler(callback) 18 | if (Package['storyteller:seznam-oauth']) { 19 | Package['storyteller:seznam-oauth'].Seznam.requestCredential(options, credentialRequestCompleteCallback) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /community-services/slack.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | 4 | Meteor.linkWithSlack = function (options, callback) { 5 | if (!Meteor.userId()) { 6 | throw new Meteor.Error(402, 'Please login to an existing account before link.') 7 | } 8 | if (!Package['acemtp:accounts-slack']) { 9 | throw new Meteor.Error(403, 'Please include acemtp:accounts-slack package') 10 | } 11 | 12 | if (!callback && typeof options === 'function') { 13 | callback = options 14 | options = null 15 | } 16 | 17 | const credentialRequestCompleteCallback = Accounts.oauth.linkCredentialRequestCompleteHandler(callback) 18 | Package['acemtp:accounts-slack'].Slack.requestCredential(options, credentialRequestCompleteCallback) 19 | } 20 | -------------------------------------------------------------------------------- /community-services/soundcloud.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | 4 | Meteor.linkWithSoundcloud = function (options, callback) { 5 | if (!Meteor.userId()) { 6 | throw new Meteor.Error(402, 'Please login to an existing account before link.') 7 | } 8 | if (!Package['garbolino:accounts-soundcloud']) { 9 | throw new Meteor.Error(403, 'Please include garbolino:accounts-soundcloud package') 10 | } 11 | 12 | if (!callback && typeof options === 'function') { 13 | callback = options 14 | options = null 15 | } 16 | 17 | const credentialRequestCompleteCallback = Accounts.oauth.linkCredentialRequestCompleteHandler(callback) 18 | Package['garbolino:accounts-soundcloud'].Soundcloud.requestCredential(options, credentialRequestCompleteCallback) 19 | } 20 | -------------------------------------------------------------------------------- /community-services/spotify.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | 4 | Meteor.linkWithSpotify = function (options, callback) { 5 | if (!Meteor.userId()) { 6 | throw new Meteor.Error(402, 'Please login to an existing account before link.') 7 | } 8 | if (!Package['xinranxiao:spotify'] || !Package['xinranxiao:accounts-spotify']) { 9 | throw new Meteor.Error(403, 'Please include xinranxiao:meteor-spotify package') 10 | } 11 | 12 | if (!callback && typeof options === 'function') { 13 | callback = options 14 | options = null 15 | } 16 | 17 | const credentialRequestCompleteCallback = Accounts.oauth.linkCredentialRequestCompleteHandler(callback) 18 | Package['xinranxiao:accounts-spotify'].Spotify.requestCredential(options, credentialRequestCompleteCallback) 19 | } 20 | -------------------------------------------------------------------------------- /community-services/twitch.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | 4 | Meteor.linkWithTwitch = function (options, callback) { 5 | if (!Meteor.userId()) { 6 | throw new Meteor.Error(402, 'Please login to an existing account before link.') 7 | } 8 | if (!Package['alexbeauchemin:accounts-twitch']) { 9 | throw new Meteor.Error(403, 'Please include lexbeauchemin:accounts-twitch packages') 10 | } 11 | 12 | if (!callback && typeof options === 'function') { 13 | callback = options 14 | options = null 15 | } 16 | 17 | const credentialRequestCompleteCallback = Accounts.oauth.linkCredentialRequestCompleteHandler(callback) 18 | Package['alexbeauchemin:accounts-twitch'].TwitchAccounts.requestCredential( 19 | options, 20 | credentialRequestCompleteCallback 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /community-services/venmo.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | 4 | Meteor.linkWithVenmo = function (options, callback) { 5 | if (!Meteor.userId()) { 6 | throw new Meteor.Error(402, 'Please login to an existing account before link.') 7 | } 8 | if (!Package['pcooney10:accounts-venmo']) { 9 | throw new Meteor.Error(403, 'Please include pcooney10:accounts-venmo package') 10 | } 11 | 12 | if (!callback && typeof options === 'function') { 13 | callback = options 14 | options = null 15 | } 16 | 17 | const credentialRequestCompleteCallback = Accounts.oauth.linkCredentialRequestCompleteHandler(callback) 18 | Package['pcooney10:accounts-venmo'].Venmo.requestCredential(options, credentialRequestCompleteCallback) 19 | } 20 | -------------------------------------------------------------------------------- /community-services/vk.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | 4 | Meteor.linkWithVk = function (options, callback) { 5 | if (!Meteor.userId()) { 6 | throw new Meteor.Error(402, 'Please login to an existing account before link.') 7 | } 8 | if (!Package['mrt:accounts-vk']) { 9 | throw new Meteor.Error(403, 'Please include mrt:accounts-vk package') 10 | } 11 | 12 | if (!callback && typeof options === 'function') { 13 | callback = options 14 | options = null 15 | } 16 | 17 | const credentialRequestCompleteCallback = Accounts.oauth.linkCredentialRequestCompleteHandler(callback) 18 | Package['mrt:accounts-vk'].VK.requestCredential(options, credentialRequestCompleteCallback) 19 | } 20 | -------------------------------------------------------------------------------- /community-services/web3.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | 3 | /** 4 | * 5 | * @param options.linkMessage { String } Message to display on wallet confirmation 6 | * @param callback { Function } 7 | */ 8 | Meteor.linkWithWeb3 = function (options, callback) { 9 | if (!Meteor.userId()) { 10 | throw new Meteor.Error( 11 | 402, 12 | 'Please login to an existing account before link.' 13 | ) 14 | } 15 | if (!Package['freedombase:web3-login']) { 16 | throw new Meteor.Error( 17 | 403, 18 | 'Please include freedombase:web3-login package.' 19 | ) 20 | } 21 | 22 | if (!callback && typeof options === 'function') { 23 | callback = options 24 | options = null 25 | } 26 | 27 | // Since the flow for Web3 is different we are using a custom flow here. 28 | const credentialRequestCompleteCallback = (error, address) => { 29 | if (error) { 30 | throw error 31 | } 32 | if (address) { 33 | Meteor.call('bozhao:linkAccountsWeb3', address, callback) 34 | } 35 | } 36 | Package['freedombase:web3-login'].loginWithWeb3( 37 | { 38 | loginMessage: 39 | options?.linkMessage || 40 | `Please verify that you want to link your wallet to ${Meteor.absoluteUrl()}.`, 41 | onlyReturnAddress: true 42 | }, 43 | credentialRequestCompleteCallback 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /community-services/wechat.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | 4 | Meteor.linkWithWechat = function (options, callback) { 5 | if (!Meteor.userId()) { 6 | throw new Meteor.Error(402, 'Please login to an existing account before link.') 7 | } 8 | if (!Package['leonzhang1109:accounts-wechat']) { 9 | throw new Meteor.Error(403, 'Please include leonzhang1109:accounts-wechat package') 10 | } 11 | 12 | if (!callback && typeof options === 'function') { 13 | callback = options 14 | options = null 15 | } 16 | 17 | const credentialRequestCompleteCallback = Accounts.oauth.linkCredentialRequestCompleteHandler(callback) 18 | Package['leonzhang1109:accounts-wechat'].Wechat.requestCredential(options, credentialRequestCompleteCallback) 19 | } 20 | -------------------------------------------------------------------------------- /core-services/facebook.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | 4 | Meteor.linkWithFacebook = function (options, callback) { 5 | if (!Meteor.userId()) { 6 | throw new Meteor.Error(402, 'Please login to an existing account before link.') 7 | } 8 | 9 | let facebookPackage 10 | if (Package.facebook) { 11 | facebookPackage = Package.facebook 12 | } else if (Package['facebook-oauth']) { 13 | facebookPackage = Package['facebook-oauth'] 14 | } 15 | 16 | if (Meteor.isCordova) { 17 | if (!Package['btafel:accounts-facebook-cordova']) { 18 | throw new Meteor.Error(403, 'Please include btafel:accounts-facebook-cordova package or cordova-fb package') 19 | } 20 | } else { 21 | if (!facebookPackage) { 22 | throw new Meteor.Error(403, 'Please include accounts-facebook and facebook-oauth package or cordova-fb package') 23 | } 24 | } 25 | 26 | if (!callback && typeof options === 'function') { 27 | callback = options 28 | options = null 29 | } 30 | 31 | const credentialRequestCompleteCallback = Accounts.oauth.linkCredentialRequestCompleteHandler(callback) 32 | facebookPackage.Facebook.requestCredential(options, credentialRequestCompleteCallback) 33 | } 34 | -------------------------------------------------------------------------------- /core-services/github.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | 4 | Meteor.linkWithGithub = function (options, callback) { 5 | if (!Meteor.userId()) { 6 | throw new Meteor.Error(402, 'Please login to an existing account before link.') 7 | } 8 | if (!Package['accounts-github'] && (!Package.github && !Package['github-oauth'])) { 9 | throw new Meteor.Error(403, 'Please include accounts-github and github package') 10 | } 11 | 12 | const githubOAuthPackageName = Package.github ? 'github' : 'github-oauth' 13 | 14 | if (!callback && typeof options === 'function') { 15 | callback = options 16 | options = null 17 | } 18 | 19 | const credentialRequestCompleteCallback = Accounts.oauth.linkCredentialRequestCompleteHandler(callback) 20 | Package[githubOAuthPackageName].Github.requestCredential(options, credentialRequestCompleteCallback) 21 | } 22 | -------------------------------------------------------------------------------- /core-services/google.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | 4 | Meteor.linkWithGoogle = function (options, callback) { 5 | if (!Meteor.userId()) { 6 | throw new Meteor.Error(402, 'Please login to an existing account before link.') 7 | } 8 | if (!Package['accounts-google'] && !Package.google) { 9 | throw new Meteor.Error(403, 'Please include accounts-google and google package') 10 | } 11 | 12 | if (!callback && typeof options === 'function') { 13 | callback = options 14 | options = null 15 | } 16 | 17 | const credentialRequestCompleteCallback = Accounts.oauth.linkCredentialRequestCompleteHandler(callback) 18 | if (Meteor.isCordova) { 19 | window.plugins.googleplus.login( 20 | {}, 21 | function (serviceData) { 22 | Meteor.call('cordovaGoogle', 'google', serviceData) 23 | }, 24 | function (err) { 25 | callback(err) 26 | } 27 | ) 28 | } else { 29 | Package['google-oauth'].Google.requestCredential(options, credentialRequestCompleteCallback) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /core-services/meetup.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | 4 | Meteor.linkWithMeetup = function (options, callback) { 5 | if (!Meteor.userId()) { 6 | throw new Meteor.Error(402, 'Please login to an existing account before link.') 7 | } 8 | if (!Package['accounts-meetup'] && !Package.meetup) { 9 | throw new Meteor.Error(403, 'Please include accounts-meetup and meetup package') 10 | } 11 | 12 | if (!callback && typeof options === 'function') { 13 | callback = options 14 | options = null 15 | } 16 | 17 | const credentialRequestCompleteCallback = Accounts.oauth.linkCredentialRequestCompleteHandler(callback) 18 | Package.meetup.Meetup.requestCredential(options, credentialRequestCompleteCallback) 19 | } 20 | -------------------------------------------------------------------------------- /core-services/meteor_developer.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | 4 | Meteor.linkWithMeteorDeveloperAccount = function (options, callback) { 5 | if (!Meteor.userId()) { 6 | throw new Meteor.Error(402, 'Please login to an existing account before link.') 7 | } 8 | if (!Package['accounts-meteor-developer'] && !Package['meteor-developer-oauth']) { 9 | throw new Meteor.Error(403, 'Please include accounts-meteor-developer package') 10 | } 11 | 12 | if (!callback && typeof options === 'function') { 13 | callback = options 14 | options = null 15 | } 16 | 17 | const credentialRequestCompleteCallback = Accounts.oauth.linkCredentialRequestCompleteHandler(callback) 18 | Package['meteor-developer-oauth'].MeteorDeveloperAccounts.requestCredential(options, credentialRequestCompleteCallback) 19 | } 20 | -------------------------------------------------------------------------------- /core-services/twitter.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | 4 | Meteor.linkWithTwitter = function (options, callback) { 5 | if (!Meteor.userId()) { 6 | throw new Meteor.Error(402, 'Please login to an existing account before link.') 7 | } 8 | 9 | let twitterPackage 10 | if (Package.twitter) { 11 | twitterPackage = Package.twitter 12 | } else if (Package['twitter-oauth']) { 13 | twitterPackage = Package['twitter-oauth'] 14 | } 15 | 16 | if (!Package['accounts-twitter'] && !twitterPackage) { 17 | throw new Meteor.Error(403, 'Please include accounts-twitter and twitter package') 18 | } 19 | 20 | if (!callback && typeof options === 'function') { 21 | callback = options 22 | options = null 23 | } 24 | 25 | const credentialRequestCompleteCallback = Accounts.oauth.linkCredentialRequestCompleteHandler(callback) 26 | twitterPackage.Twitter.requestCredential(options, credentialRequestCompleteCallback) 27 | } 28 | -------------------------------------------------------------------------------- /core-services/weibo.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | 4 | Meteor.linkWithWeibo = function (options, callback) { 5 | if (!Meteor.userId()) { 6 | throw new Meteor.Error(402, 'Please login to an existing account before link.') 7 | } 8 | 9 | let weiboPackage 10 | if (Package.weibo) { 11 | weiboPackage = Package.weibo 12 | } else if (Package['weibo-oauth']) { 13 | weiboPackage = Package['weibo-oauth'] 14 | } 15 | 16 | if (!Package['accounts-weibo'] || !weiboPackage) { 17 | throw new Meteor.Error(403, 'Please include accounts-weibo and weibo package') 18 | } 19 | 20 | if (!callback && typeof options === 'function') { 21 | callback = options 22 | options = null 23 | } 24 | 25 | const credentialRequestCompleteCallback = Accounts.oauth.linkCredentialRequestCompleteHandler(callback) 26 | weiboPackage.Weibo.requestCredential(options, credentialRequestCompleteCallback) 27 | } 28 | -------------------------------------------------------------------------------- /example/.meteor/.finished-upgraders: -------------------------------------------------------------------------------- 1 | # This file contains information which helps Meteor properly upgrade your 2 | # app when you run 'meteor update'. You should check it into version control 3 | # with your project. 4 | 5 | notices-for-0.9.0 6 | notices-for-0.9.1 7 | 0.9.4-platform-file 8 | notices-for-facebook-graph-api-2 9 | 1.2.0-standard-minifiers-package 10 | 1.2.0-meteor-platform-split 11 | 1.2.0-cordova-changes 12 | 1.2.0-breaking-changes 13 | 1.3.0-split-minifiers-package 14 | 1.4.0-remove-old-dev-bundle-link 15 | 1.4.1-add-shell-server-package 16 | 1.4.3-split-account-service-packages 17 | 1.5-add-dynamic-import-package 18 | 1.7-split-underscore-from-meteor-base 19 | 1.8.3-split-jquery-from-blaze 20 | -------------------------------------------------------------------------------- /example/.meteor/.gitignore: -------------------------------------------------------------------------------- 1 | local 2 | -------------------------------------------------------------------------------- /example/.meteor/.id: -------------------------------------------------------------------------------- 1 | # This file contains a token that is unique to your project. 2 | # Check it into your repository along with the rest of this directory. 3 | # It can be used for purposes such as: 4 | # - ensuring you don't accidentally deploy one app on top of another 5 | # - providing package authors with aggregated statistics 6 | 7 | 175irt21r4q3ri1kd365e 8 | -------------------------------------------------------------------------------- /example/.meteor/cordova-plugins: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Meteor-Community-Packages/meteor-link-accounts/f52366f1b38f7c3141cf9def9c4baf7c3db782ec/example/.meteor/cordova-plugins -------------------------------------------------------------------------------- /example/.meteor/packages: -------------------------------------------------------------------------------- 1 | # Meteor packages used by this project, one per line. 2 | # 3 | # 'meteor add' and 'meteor remove' will edit this file for you, 4 | # but you can also edit it by hand. 5 | 6 | standard-app-packages 7 | autopublish@1.0.7 8 | insecure@1.0.7 9 | accounts-base@1.6.0 10 | accounts-ui@1.3.1 11 | accounts-github@1.4.3 12 | accounts-twitter@1.4.2 13 | bozhao:link-accounts 14 | 15 | mongo@1.10.0 16 | service-configuration@1.0.11 17 | standard-minifier-css 18 | standard-minifier-js 19 | shell-server 20 | github-config-ui 21 | github-oauth 22 | twitter-config-ui 23 | dynamic-import 24 | -------------------------------------------------------------------------------- /example/.meteor/platforms: -------------------------------------------------------------------------------- 1 | server 2 | browser 3 | -------------------------------------------------------------------------------- /example/.meteor/release: -------------------------------------------------------------------------------- 1 | METEOR@1.10.2 2 | -------------------------------------------------------------------------------- /example/.meteor/versions: -------------------------------------------------------------------------------- 1 | accounts-base@1.6.0 2 | accounts-github@1.4.3 3 | accounts-oauth@1.2.0 4 | accounts-twitter@1.4.2 5 | accounts-ui@1.3.1 6 | accounts-ui-unstyled@1.4.2 7 | allow-deny@1.1.0 8 | autopublish@1.0.7 9 | autoupdate@1.6.0 10 | babel-compiler@7.5.3 11 | babel-runtime@1.5.0 12 | base64@1.0.12 13 | binary-heap@1.0.11 14 | blaze@2.3.4 15 | blaze-tools@1.0.10 16 | boilerplate-generator@1.7.0 17 | bozhao:link-accounts@2.1.0 18 | caching-compiler@1.2.2 19 | caching-html-compiler@1.1.3 20 | callback-hook@1.3.0 21 | check@1.3.1 22 | ddp@1.4.0 23 | ddp-client@2.3.3 24 | ddp-common@1.4.0 25 | ddp-rate-limiter@1.0.7 26 | ddp-server@2.3.1 27 | deps@1.0.12 28 | diff-sequence@1.1.1 29 | dynamic-import@0.5.2 30 | ecmascript@0.14.3 31 | ecmascript-runtime@0.7.0 32 | ecmascript-runtime-client@0.10.0 33 | ecmascript-runtime-server@0.9.0 34 | ejson@1.1.1 35 | fastclick@1.0.13 36 | fetch@0.1.1 37 | geojson-utils@1.0.10 38 | github-config-ui@1.0.1 39 | github-oauth@1.2.3 40 | html-tools@1.0.11 41 | htmljs@1.0.11 42 | http@1.4.2 43 | id-map@1.1.0 44 | insecure@1.0.7 45 | inter-process-messaging@0.1.1 46 | jquery@1.11.11 47 | launch-screen@1.2.0 48 | less@2.8.0 49 | livedata@1.0.18 50 | localstorage@1.2.0 51 | logging@1.1.20 52 | meteor@1.9.3 53 | meteor-platform@1.2.6 54 | minifier-css@1.5.0 55 | minifier-js@2.6.0 56 | minimongo@1.6.0 57 | mobile-status-bar@1.1.0 58 | modern-browsers@0.1.5 59 | modules@0.15.0 60 | modules-runtime@0.12.0 61 | mongo@1.10.0 62 | mongo-decimal@0.1.1 63 | mongo-dev-server@1.1.0 64 | mongo-id@1.0.7 65 | npm-mongo@3.7.0 66 | oauth@1.3.0 67 | oauth1@1.3.0 68 | oauth2@1.3.0 69 | observe-sequence@1.0.16 70 | ordered-dict@1.1.0 71 | promise@0.11.2 72 | random@1.2.0 73 | rate-limit@1.0.9 74 | reactive-dict@1.3.0 75 | reactive-var@1.0.11 76 | reload@1.3.0 77 | retry@1.1.0 78 | routepolicy@1.1.0 79 | service-configuration@1.0.11 80 | session@1.2.0 81 | shell-server@0.5.0 82 | socket-stream-client@0.3.0 83 | spacebars@1.0.15 84 | spacebars-compiler@1.1.3 85 | standard-app-packages@1.0.9 86 | standard-minifier-css@1.6.0 87 | standard-minifier-js@2.6.0 88 | templating@1.3.2 89 | templating-compiler@1.3.3 90 | templating-runtime@1.3.2 91 | templating-tools@1.1.2 92 | tracker@1.2.0 93 | twitter-config-ui@1.0.0 94 | twitter-oauth@1.2.0 95 | ui@1.0.13 96 | underscore@1.0.10 97 | url@1.3.0 98 | webapp@1.9.1 99 | webapp-hashing@1.0.9 100 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # How to run example # 2 | 3 | 1. Install Meteor curl https://install.meteor.com/ | sh 4 | 2. Run example project by "meteor" command 5 | 3. Setup external services' configuration by click sign-in button and follow on 6 | screen instructions. 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/example.html: -------------------------------------------------------------------------------- 1 | 2 | Link Account example 3 | 4 | 5 | 6 |
7 |

1. Log in with any non Github service through login button

8 | {{> loginButtons}} 9 |
10 | 11 |
12 | {{> linkTemplate}} 13 |
14 | 15 | 16 | 34 | -------------------------------------------------------------------------------- /example/example.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | import { Template } from 'meteor/templating' 4 | 5 | if (Meteor.isClient) { 6 | Template.linkTemplate.events({ 7 | 'click .link-github': function () { 8 | Meteor.linkWithGithub() 9 | }, 10 | 'click .unlink-github': function () { 11 | Meteor.call('_accounts/unlink/service', Meteor.userId(), 'github') 12 | } 13 | }) 14 | 15 | Template.linkTemplate.helpers({ 16 | services: function () { 17 | const user = Meteor.user() 18 | if (user) { 19 | return Object.keys(user.services) 20 | } 21 | } 22 | }) 23 | } 24 | 25 | if (Meteor.isServer) { 26 | // XXX input your api keys here or follow the onscreen popup instructions 27 | /* 28 | ServiceConfiguration.configurations.upsert({service: 'github'}, { 29 | $set: { 30 | clientId: 'CLIENT_ID', 31 | secret: 'SECRET', 32 | loginStyle: 'popup' 33 | } 34 | }); 35 | 36 | ServiceConfiguration.configurations.upsert({service: 'twitter'}, { 37 | $set: { 38 | api_key: 'API_KEY', 39 | api_secret: 'API_SECRET' 40 | } 41 | }); 42 | */ 43 | Meteor.methods({ 44 | '_accounts/unlink/service': function (userId, serviceName) { 45 | Accounts.unlinkService(userId, serviceName) 46 | } 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /link_accounts_client.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | import { OAuth } from 'meteor/oauth' 4 | import './core-services/facebook' 5 | import './core-services/github' 6 | import './core-services/google' 7 | import './core-services/meetup' 8 | import './core-services/meteor_developer' 9 | import './core-services/twitter' 10 | import './core-services/weibo' 11 | import './community-services/angellist' 12 | import './community-services/apple' 13 | import './community-services/dropbox' 14 | import './community-services/discord' 15 | import './community-services/edmodo' 16 | import './community-services/instagram' 17 | import './community-services/linkedin' 18 | import './community-services/mailru' 19 | import './community-services/qq' 20 | import './community-services/ok' 21 | import './community-services/slack' 22 | import './community-services/spotify' 23 | import './community-services/soundcloud' 24 | import './community-services/twitch' 25 | import './community-services/venmo' 26 | import './community-services/vk' 27 | import './community-services/wechat' 28 | import './community-services/line' 29 | import './community-services/office365' 30 | import './community-services/web3' 31 | import './community-services/betapass' 32 | import './community-services/seznam' 33 | 34 | Accounts.oauth.tryLinkAfterPopupClosed = function ( 35 | credentialToken, 36 | callback, 37 | shouldRetry = true 38 | ) { 39 | const credentialSecret = OAuth._retrieveCredentialSecret(credentialToken) 40 | 41 | if (!credentialSecret) { 42 | if (!shouldRetry) { 43 | return 44 | } 45 | Meteor.setTimeout( 46 | () => 47 | Accounts.oauth.tryLinkAfterPopupClosed( 48 | credentialToken, 49 | callback, 50 | false 51 | ), 52 | 500 53 | ) 54 | return 55 | } 56 | 57 | Accounts.callLoginMethod({ 58 | methodArguments: [ 59 | { 60 | link: { 61 | credentialToken, 62 | credentialSecret 63 | } 64 | } 65 | ], 66 | userCallback: 67 | callback && 68 | function (err) { 69 | // Allow server to specify subclass of errors. We should come 70 | // up with a more generic way to do this! 71 | if ( 72 | err && 73 | err instanceof Meteor.Error && 74 | err.error === Accounts.LoginCancelledError.numericError 75 | ) { 76 | callback(new Accounts.LoginCancelledError(err.details)) 77 | } else { 78 | callback(err) 79 | } 80 | } 81 | }) 82 | } 83 | 84 | Accounts.oauth.linkCredentialRequestCompleteHandler = function (callback) { 85 | return function (credentialTokenOrError) { 86 | if (credentialTokenOrError && credentialTokenOrError instanceof Error) { 87 | callback && callback(credentialTokenOrError) 88 | } else { 89 | Accounts.oauth.tryLinkAfterPopupClosed(credentialTokenOrError, callback) 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /link_accounts_server.js: -------------------------------------------------------------------------------- 1 | import { Meteor } from 'meteor/meteor' 2 | import { Accounts } from 'meteor/accounts-base' 3 | import { Mongo } from 'meteor/mongo' 4 | import { check, Match } from 'meteor/check' 5 | import { OAuth } from 'meteor/oauth' 6 | import { Hook } from 'meteor/callback-hook' 7 | 8 | /** 9 | * Hooks definition and registration 10 | */ 11 | Accounts._onLink = new Hook({ 12 | bindEnvironment: false, 13 | debugPrintExceptions: 'onLink callback' 14 | }) 15 | Accounts.onLink = (func) => Accounts._onLink.register(func) 16 | 17 | Accounts._beforeLink = new Hook({ 18 | bindEnvironment: false, 19 | debugPrintExceptions: 'beforeLink callback' 20 | }) 21 | Accounts.beforeLink = (func) => Accounts._beforeLink.register(func) 22 | 23 | Accounts._onUnlink = new Hook({ 24 | bindEnvironment: false, 25 | debugPrintExceptions: 'onUnlink callback' 26 | }) 27 | Accounts.onUnlink = (func) => Accounts._onUnlink.register(func) 28 | 29 | Accounts.registerLoginHandler(async function (options) { 30 | if (!options.link) return undefined 31 | 32 | check(options.link, { 33 | credentialToken: String, 34 | // When an error occurs while retrieving the access token, we store 35 | // the error in the pending credentials table, with a secret of 36 | // null. The client can call the login method with a secret of null 37 | // to retrieve the error. 38 | credentialSecret: Match.OneOf(null, String) 39 | }) 40 | 41 | const result = await OAuth.retrieveCredential( 42 | options.link.credentialToken, 43 | options.link.credentialSecret 44 | ) 45 | if (!result) { 46 | return { 47 | type: 'link', 48 | error: new Meteor.Error( 49 | Accounts.LoginCancelledError.numericError, 50 | 'No matching link attempt found' 51 | ) 52 | } 53 | } 54 | 55 | if (result instanceof Error || result instanceof Meteor.Error) throw result 56 | else { 57 | return await Accounts.LinkUserFromExternalService( 58 | result.serviceName, 59 | result.serviceData, 60 | result.options 61 | ) 62 | } 63 | }) 64 | 65 | Meteor.methods({ 66 | // TODO namespace this method for next major release 67 | cordovaGoogle: async function (serviceName, serviceData) { 68 | check(serviceName, String) 69 | check(serviceData, Object) 70 | await Accounts.LinkUserFromExternalService(serviceName, serviceData, {}) // passing empty object cause in any case it is not used 71 | }, 72 | 'bozhao:linkAccountsWeb3': async function (address) { 73 | check(address, String) 74 | const user = await Meteor.users.findOneAsync({ 'services.web3.address': address }) 75 | if (user) throw new Meteor.Error('500', 'This address is already assigned!') 76 | return await Accounts.LinkUserFromExternalService( 77 | 'web3', 78 | { id: address, address, verified: false }, 79 | {} 80 | ) 81 | } 82 | }) 83 | 84 | Accounts.LinkUserFromExternalService = async function ( 85 | serviceName, 86 | serviceData, 87 | options 88 | ) { 89 | options = { ...options } 90 | 91 | // We probably throw an error instead of call update or create here. 92 | if (!Meteor.userId()) { 93 | return new Meteor.Error( 94 | 'You must be logged in to use LinkUserFromExternalService' 95 | ) 96 | } 97 | 98 | if (serviceName === 'password' || serviceName === 'resume') { 99 | throw new Meteor.Error( 100 | "Can't use LinkUserFromExternalService with internal service: " + 101 | serviceName 102 | ) 103 | } 104 | if (!(serviceData.id || serviceData.userId)) { 105 | throw new Meteor.Error("'id' missing from service data for: " + serviceName) 106 | } 107 | 108 | const user = await Meteor.userAsync() 109 | 110 | if (!user) { 111 | return new Meteor.Error('User not found for LinkUserFromExternalService') 112 | } 113 | const checkExistingSelector = {} 114 | if (serviceData.userId) { 115 | serviceData.id = serviceData.userId 116 | delete serviceData.userId 117 | } 118 | checkExistingSelector['services.' + serviceName + '.id'] = serviceData.id 119 | 120 | const existingUsers = await Meteor.users.find(checkExistingSelector).fetchAsync() 121 | if (existingUsers.length) { 122 | existingUsers.forEach(function (existingUser) { 123 | if (existingUser._id !== Meteor.userId()) { 124 | throw new Meteor.Error( 125 | `Provided ${serviceName} account is already in use by other user` 126 | ) 127 | } 128 | }) 129 | } 130 | 131 | // we do not allow link another account from existing service. 132 | // TODO maybe we can override this? 133 | if ( 134 | user.services && 135 | user.services[serviceName] && 136 | user.services[serviceName].id !== serviceData.id 137 | ) { 138 | return new Meteor.Error( 139 | 'User can link only one account to service: ' + serviceName 140 | ) 141 | } else { 142 | const setAttrs = {} 143 | 144 | // Before link hook 145 | let shouldStop = false 146 | Accounts._beforeLink.forEach((callback) => { 147 | // eslint-disable-next-line n/no-callback-literal 148 | const result = callback({ 149 | type: serviceName, 150 | serviceData, 151 | user, 152 | serviceOptions: options 153 | }) 154 | if (!result) shouldStop = true 155 | return !!result 156 | }) 157 | if (shouldStop) return null 158 | 159 | Object.keys(serviceData).forEach((key) => { 160 | setAttrs['services.' + serviceName + '.' + key] = serviceData[key] 161 | }) 162 | 163 | const updated = await Meteor.users.updateAsync(user._id, { $set: setAttrs }) 164 | if (updated !== 1) { 165 | throw new Meteor.Error( 166 | `Failed to link user ${Meteor.userId()} with ${serviceName} account` 167 | ) 168 | } 169 | 170 | // On link hook 171 | Accounts._onLink.forEachAsync(async (callback) => { 172 | const user = await Meteor.userAsync() 173 | 174 | // eslint-disable-next-line n/no-callback-literal 175 | callback({ 176 | type: serviceName, 177 | serviceData, 178 | user, 179 | serviceOptions: options 180 | }) 181 | return true 182 | }) 183 | 184 | return { 185 | type: serviceName, 186 | userId: user._id 187 | } 188 | } 189 | } 190 | 191 | Accounts.unlinkService = async function (userId, serviceName, cb) { 192 | check(userId, Match.OneOf(String, Mongo.ObjectID)) 193 | if (typeof serviceName !== 'string') { 194 | throw new Meteor.Error('Service name must be string') 195 | } 196 | const user = await Meteor.users.findOneAsync({ _id: userId }) 197 | if (serviceName === 'resume' || serviceName === 'password') { 198 | throw new Meteor.Error( 199 | 'Internal services cannot be unlinked: ' + serviceName 200 | ) 201 | } 202 | 203 | if (user.services[serviceName]) { 204 | const newServices = { ...user.services } 205 | delete newServices[serviceName] 206 | await Meteor.users.updateAsync( 207 | { _id: user._id }, 208 | { $set: { services: newServices } }, 209 | function (result) { 210 | if (cb && typeof cb === 'function') { 211 | cb(result) 212 | } 213 | } 214 | ) 215 | // On unlink hook 216 | Accounts._onUnlink.forEachAsync(async (callback) => { 217 | const user = await Meteor.userAsync() 218 | // eslint-disable-next-line n/no-callback-literal 219 | callback({ type: serviceName, user }) 220 | return true 221 | }) 222 | } else { 223 | throw new Meteor.Error(500, 'no service') 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /package.js: -------------------------------------------------------------------------------- 1 | Package.describe({ 2 | summary: 'Meteor external service link system', 3 | version: '3.0.1', 4 | git: 'https://github.com/Meteor-Community-Packages/meteor-link-accounts', 5 | name: 'bozhao:link-accounts', 6 | description: 'Link social accounts for Meteor' 7 | }) 8 | Package.onUse(function (api) { 9 | api.versionsFrom(['3.0']) 10 | 11 | api.imply('accounts-base', ['client', 'server']) 12 | api.use(['ecmascript', 'check', 'accounts-oauth', 'oauth']) 13 | api.use('callback-hook', 'server') 14 | 15 | api.mainModule('link_accounts_client.js', 'client') 16 | api.mainModule('link_accounts_server.js', 'server') 17 | }) 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "meteor-link-accounts", 3 | "version": "3.0.1", 4 | "private": true, 5 | "description": "Link social accounts for Meteor", 6 | "main": "package.js", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/Meteor-Community-Packages/meteor-link-accounts.git" 10 | }, 11 | "bugs": { 12 | "url": "https://github.com/Meteor-Community-Packages/meteor-link-accounts/issues" 13 | }, 14 | "homepage": "https://github.com/Meteor-Community-Packages/meteor-link-accounts#readme", 15 | "readme": "README.md", 16 | "scripts": { 17 | "test": "npm run lint && meteor test-packages ./", 18 | "lint": "./node_modules/.bin/standard --fix", 19 | "lint-test-ci": "./node_modules/.bin/standard", 20 | "publish": "meteor npm i && npm prune --omit=dev && meteor publish && meteor npm i", 21 | "all-contributors": "./node_modules/.bin/all-contributors", 22 | "all-contributors-generate": "./node_modules/.bin/all-contributors generate" 23 | }, 24 | "license": "MIT", 25 | "standard": { 26 | "parser": "@babel/eslint-parser", 27 | "globals": [ 28 | "Package" 29 | ] 30 | }, 31 | "funding": { 32 | "type": "GitHub", 33 | "url": "https://github.com/sponsors/StorytellerCZ" 34 | }, 35 | "devDependencies": { 36 | "@babel/core": "^7.25.2", 37 | "@babel/eslint-parser": "^7.25.1", 38 | "@babel/preset-env": "^7.25.4", 39 | "@types/meteor": "^2.9.8", 40 | "all-contributors-cli": "^6.26.1", 41 | "standard": "^17.1.0" 42 | }, 43 | "dependencies": {} 44 | } 45 | --------------------------------------------------------------------------------