Skip to content

Commit

Permalink
Merge pull request #106 from StorytellerCZ/feature/web3
Browse files Browse the repository at this point in the history
Added freedombase:web3-login
  • Loading branch information
StorytellerCZ authored May 10, 2022
2 parents acc0eae + 3c2724f commit 5f6f5d7
Show file tree
Hide file tree
Showing 9 changed files with 148 additions and 29 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Changelog

## v. 2.5.0 - xx.5.2022
## v. 2.6.0 - XX.5.2022
* Added `freedombase:web3-login`
* Slight code re-format for better readability

## v. 2.5.0 - 9.5.2022
* Make hooks stoppable
* Fix commit script
* Added Apple provider for `quave:accounts-apple` or `bigowl:accounts-apple` package
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ You will call this on the page where you allow your users to connect to other se

`options` is expecting configuration object. Most often that is going to be: `{ loginStyle: 'popup' }`

##### `freedombase:web3-login`
The `options` object accepts `linkMessage` key where you can set message for signature.

### Server side
#### Accounts.unlinkService(userId, serviceName)
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).
Expand Down Expand Up @@ -91,6 +94,7 @@ Called after user is unlinking the service. The hook will receive object with th
* storyteller:accounts-line
* lindoelio:accounts-office365 / ermlab:accounts-office365
* quave:accounts-apple / bigowl:accounts-apple
* freedombase:web3-login

## License
MIT
40 changes: 40 additions & 0 deletions community-services/web3.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Meteor } from 'meteor/meteor'

/**
*
* @param options.linkMessage { String } Message to display on wallet confirmation
* @param callback { Function }
*/
Meteor.linkWithWeb3 = function (options, callback) {
if (!Meteor.userId()) {
throw new Meteor.Error(
402,
'Please login to an existing account before link.'
)
}
if (!Package['freedombase:web3-login']) {
throw new Meteor.Error(
403,
'Please include freedombase:web3-login package.'
)
}

if (!callback && typeof options === 'function') {
callback = options
options = null
}

// Since the flow for Web3 is different we are using a custom flow here.
const credentialRequestCompleteCallback = (error, address) => {
if (error.error === 403) {
Meteor.call('bozhao:linkAccountsWeb3', address, callback)
} else {
throw error
}
}
Package['freedombase:web3-login'].loginWithWeb3(
options?.linkMessage ||
`Please verify that you want to link your wallet to ${Meteor.absoluteUrl()}.`,
credentialRequestCompleteCallback
)
}
7 changes: 6 additions & 1 deletion link_accounts_client.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import './community-services/vk'
import './community-services/wechat'
import './community-services/line'
import './community-services/office365'
import './community-services/web3'

Accounts.oauth.tryLinkAfterPopupClosed = function (credentialToken, callback) {
const credentialSecret = OAuth._retrieveCredentialSecret(credentialToken)
Expand All @@ -44,7 +45,11 @@ Accounts.oauth.tryLinkAfterPopupClosed = function (credentialToken, callback) {
function (err) {
// Allow server to specify subclass of errors. We should come
// up with a more generic way to do this!
if (err && err instanceof Meteor.Error && err.error === Accounts.LoginCancelledError.numericError) {
if (
err &&
err instanceof Meteor.Error &&
err.error === Accounts.LoginCancelledError.numericError
) {
callback(new Accounts.LoginCancelledError(err.details))
} else {
callback(err)
Expand Down
112 changes: 89 additions & 23 deletions link_accounts_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,32 +38,68 @@ Accounts.registerLoginHandler(function (options) {
credentialSecret: Match.OneOf(null, String)
})

const result = OAuth.retrieveCredential(options.link.credentialToken, options.link.credentialSecret)
const result = OAuth.retrieveCredential(
options.link.credentialToken,
options.link.credentialSecret
)
if (!result) {
return {
type: 'link',
error: new Meteor.Error(Accounts.LoginCancelledError.numericError, 'No matching link attempt found')
error: new Meteor.Error(
Accounts.LoginCancelledError.numericError,
'No matching link attempt found'
)
}
}

if (result instanceof Error || result instanceof Meteor.Error) throw result
else return Accounts.LinkUserFromExternalService(result.serviceName, result.serviceData, result.options)
else
return Accounts.LinkUserFromExternalService(
result.serviceName,
result.serviceData,
result.options
)
})

Meteor.methods({
// TODO namespace this method for next major release
cordovaGoogle: function (serviceName, serviceData) {
check(serviceName, String)
check(serviceData, Object)
Accounts.LinkUserFromExternalService(serviceName, serviceData, {}) // passing empty object cause in any case it is not used
},
'bozhao:linkAccountsWeb3': function (address) {
check(address, String)
Accounts.LinkUserFromExternalService(
'web3',
{ id: address, address, verified: false },
{}
)
}
})

Accounts.LinkUserFromExternalService = function (serviceName, serviceData, options) {
Accounts.LinkUserFromExternalService = function (
serviceName,
serviceData,
options
) {
options = { ...options }

// We probably throw an error instead of call update or create here.
if (!Meteor.userId()) return new Meteor.Error('You must be logged in to use LinkUserFromExternalService')

if (serviceName === 'password' || serviceName === 'resume') { throw new Meteor.Error("Can't use LinkUserFromExternalService with internal service: " + serviceName) }
if (!(serviceData.id || serviceData.userId)) { throw new Meteor.Error("'id' missing from service data for: " + serviceName) }
if (!Meteor.userId())
return new Meteor.Error(
'You must be logged in to use LinkUserFromExternalService'
)

if (serviceName === 'password' || serviceName === 'resume') {
throw new Meteor.Error(
"Can't use LinkUserFromExternalService with internal service: " +
serviceName
)
}
if (!(serviceData.id || serviceData.userId)) {
throw new Meteor.Error("'id' missing from service data for: " + serviceName)
}

const user = Meteor.user()

Expand All @@ -80,38 +116,62 @@ Accounts.LinkUserFromExternalService = function (serviceName, serviceData, optio
const existingUsers = Meteor.users.find(checkExistingSelector).fetch()
if (existingUsers.length) {
existingUsers.forEach(function (existingUser) {
if (existingUser._id !== Meteor.userId()) { throw new Meteor.Error(`Provided ${serviceName} account is already in use by other user`) }
if (existingUser._id !== Meteor.userId()) {
throw new Meteor.Error(
`Provided ${serviceName} account is already in use by other user`
)
}
})
}

// we do not allow link another account from existing service.
// TODO maybe we can override this?
if (user.services && user.services[serviceName] && user.services[serviceName].id !== serviceData.id) {
return new Meteor.Error('User can link only one account to service: ' + serviceName)
if (
user.services &&
user.services[serviceName] &&
user.services[serviceName].id !== serviceData.id
) {
return new Meteor.Error(
'User can link only one account to service: ' + serviceName
)
} else {
const setAttrs = {}

// Before link hook
let shouldStop = false
Accounts._beforeLink.each(callback => {
Accounts._beforeLink.each((callback) => {
// eslint-disable-next-line node/no-callback-literal
let result = callback({ type: serviceName, serviceData, user, serviceOptions: options })
let result = callback({
type: serviceName,
serviceData,
user,
serviceOptions: options
})
if (!result) shouldStop = true
return !!result
})
if (shouldStop) return null

Object.keys(serviceData).forEach(key => {
Object.keys(serviceData).forEach((key) => {
setAttrs['services.' + serviceName + '.' + key] = serviceData[key]
})

const updated = Meteor.users.update(user._id, { $set: setAttrs })
if (updated !== 1) { throw new Meteor.Error(`Failed to link user ${Meteor.userId()} with ${serviceName} account`) }
if (updated !== 1) {
throw new Meteor.Error(
`Failed to link user ${Meteor.userId()} with ${serviceName} account`
)
}

// On link hook
Accounts._onLink.each(callback => {
Accounts._onLink.each((callback) => {
// eslint-disable-next-line node/no-callback-literal
callback({ type: serviceName, serviceData, user: Meteor.user(), serviceOptions: options })
callback({
type: serviceName,
serviceData,
user: Meteor.user(),
serviceOptions: options
})
return true
})

Expand All @@ -129,19 +189,25 @@ Accounts.unlinkService = function (userId, serviceName, cb) {
}
const user = Meteor.users.findOne({ _id: userId })
if (serviceName === 'resume' || serviceName === 'password') {
throw new Meteor.Error('Internal services cannot be unlinked: ' + serviceName)
throw new Meteor.Error(
'Internal services cannot be unlinked: ' + serviceName
)
}

if (user.services[serviceName]) {
const newServices = { ...user.services }
delete newServices[serviceName]
Meteor.users.update({ _id: user._id }, { $set: { services: newServices } }, function (result) {
if (cb && typeof cb === 'function') {
cb(result)
Meteor.users.update(
{ _id: user._id },
{ $set: { services: newServices } },
function (result) {
if (cb && typeof cb === 'function') {
cb(result)
}
}
})
)
// On unlink hook
Accounts._onUnlink.each(callback => {
Accounts._onUnlink.each((callback) => {
// eslint-disable-next-line node/no-callback-literal
callback({ type: serviceName, user: Meteor.user() })
return true
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package.describe({
summary: 'Meteor external service link system',
version: '2.5.0',
version: '2.6.0',
git: 'https://github.com/yubozhao/meteor-link-accounts',
name: 'bozhao:link-accounts',
description: 'Link social accounts for Meteor'
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "meteor-link-accounts",
"version": "2.5.0",
"version": "2.6.0",
"private": true,
"description": "Link social accounts for Meteor",
"main": "package.js",
Expand Down
2 changes: 1 addition & 1 deletion smart.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
"description": "Link social network accounts to existing Meteor user.",
"homepage": "https://github.com/yubozhao/meteor-link-accounts",
"author": "Bozhao Yu <yubz86@gmail.com>",
"version": "2.5.0",
"version": "2.6.0",
"git": "https://github.com/yubozhao/meteor-link-accounts.git"
}

0 comments on commit 5f6f5d7

Please sign in to comment.