diff --git a/README.md b/README.md index dde2fe9..45723d4 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,14 @@ Initialize the package. ```js import { makeExecutableSchema } from 'graphql-tools'; -import { initAccounts } from 'meteor/nicolaslopezj:apollo-accounts'; +import { initAccounts } from 'meteor/cultofcoders:apollo-accounts'; import { load, getSchema } from 'graphql-load'; const { typeDefs, resolvers } = initAccounts({ loginWithFacebook: false, loginWithGoogle: false, loginWithLinkedIn: false, + loginWithPhone: false, loginWithPassword: true, overrideCreateUser: (createUser, _, args, context) { // Optionally override createUser if you need custom logic @@ -202,6 +203,92 @@ loginWithGoogle({ accessToken }, apollo); - `apollo`: Apollo client instance. +#### Phone support + +Login support using phone number and verification code. Requires ujwal:accounts-phone package. + +``` +meteor add ujwal:accounts-phone +``` + +From your client, execute the following mutation: + +```graphql +mutation createUserWithPhone { + createUserWithPhone (phone: "+11234567890", profile: {name: "A Phone User"}) { + success + } + } +``` + +Server response: +```js +{ + "data": { + "createUserWithPhone": { + "success": true + } + } +} +``` + +If Twilio has been set up on the server, a verification code will be sent to the phone via SMS. + +To login with the verification code, use the following mutation: + +```graphql +mutation loginWithPhone { + loginWithPhone (phone: "+11234567890", verificationCode: "6593") { + id + token + tokenExpires + } +} +``` + +Server response: +```js +{ + "data": { + "loginWithPhone": { + "id": "eHMzRW9B685curZ63", + "token": "Kg9mESwmEAs6xraKZ_hPv0tzOvQpTgMPhWTNXDFCet0", + "tokenExpires": 1535581386595 + } + } +} +``` + +You can use the response to store the login token: + +```js +await setTokenStore.set(id, token, new Date(tokenExpires)); +``` + +To request a new verification code, use the following mutation: + +```graphql +mutation resendPhoneVerification { + resendPhoneVerification (phone: "+11234567890") { + success + } +} +``` + +Server response: + +```js +{ + "data": { + "resendPhoneVerification": { + "success": true + } + } +} +``` + +If Twilio has been set up, then the verification code will sent via SMS. + #### onTokenChange Register a function to be called when a user is logged in or out. diff --git a/package.js b/package.js index 123b6a8..c234669 100644 --- a/package.js +++ b/package.js @@ -29,6 +29,7 @@ Package.onUse(function(api) { 'oauth', 'service-configuration', 'accounts-oauth', + 'sha', ], 'server' ); diff --git a/src/Auth.js b/src/Auth.js index ff3825e..0ee8c54 100644 --- a/src/Auth.js +++ b/src/Auth.js @@ -7,7 +7,7 @@ type LoginMethodResponse { # Token of the connection token: String! # Expiration date for the token - tokenExpires: Float! + tokenExpires: String! # The logged in user user: User } diff --git a/src/Mutation/createUserWithPhone.js b/src/Mutation/createUserWithPhone.js new file mode 100644 index 0000000..fd4684e --- /dev/null +++ b/src/Mutation/createUserWithPhone.js @@ -0,0 +1,15 @@ +import {Meteor} from 'meteor/meteor' +import {Accounts} from 'meteor/accounts-base' + +export default async function (root, options, context) { + Meteor._nodeCodeMustBeInFiber() + if (!options.phone) { + throw new Error('Phone number is required') + } + + let userId = Accounts.createUserWithPhone(options) + if(userId) { + Accounts.sendPhoneVerificationCode(userId, options.phone); + } + return ({success: !!userId}) +} diff --git a/src/Mutation/index.js b/src/Mutation/index.js index 88ed003..459cdaa 100644 --- a/src/Mutation/index.js +++ b/src/Mutation/index.js @@ -1,13 +1,16 @@ -import loginWithPassword from './loginWithPassword'; -import logout from './logout'; -import changePassword from './changePassword'; -import createUser from './createUser'; -import verifyEmail from './verifyEmail'; -import resendVerificationEmail from './resendVerificationEmail'; -import forgotPassword from './forgotPassword'; -import resetPassword from './resetPassword'; -import oauth from './oauth'; -import hasService from './oauth/hasService'; +import loginWithPassword from './loginWithPassword' +import loginWithPhone from './loginWithPhone' +import logout from './logout' +import changePassword from './changePassword' +import createUser from './createUser' +import createUserWithPhone from './createUserWithPhone' +import verifyEmail from './verifyEmail' +import resendVerificationEmail from './resendVerificationEmail' +import resendPhoneVerification from './resendPhoneVerification' +import forgotPassword from './forgotPassword' +import resetPassword from './resetPassword' +import oauth from './oauth' +import hasService from './oauth/hasService' export default function(options) { const resolvers = { @@ -35,5 +38,11 @@ export default function(options) { resolvers.resetPassword = resetPassword; } + if (hasService(options, 'phone')) { + resolvers.loginWithPhone = loginWithPhone + resolvers.createUserWithPhone = createUserWithPhone + resolvers.resendPhoneVerification = resendPhoneVerification + } + return { Mutation: resolvers }; } diff --git a/src/Mutation/loginWithPhone.js b/src/Mutation/loginWithPhone.js new file mode 100644 index 0000000..adcd59c --- /dev/null +++ b/src/Mutation/loginWithPhone.js @@ -0,0 +1,14 @@ +import callMethod from '../callMethod' +import {Meteor} from 'meteor/meteor' + + +export default async function (root, options, context) { + if (!options.phone) { + throw new Error('Phone number is required') + } + if (!options.verificationCode) { + throw new Error('Verification code is required') + } + + return callMethod(context, 'verifyPhone', options.phone, options.verificationCode) +} diff --git a/src/Mutation/oauth/hasService.js b/src/Mutation/oauth/hasService.js index b92b7ba..bfc7840 100644 --- a/src/Mutation/oauth/hasService.js +++ b/src/Mutation/oauth/hasService.js @@ -15,5 +15,9 @@ export default function (options, service) { return options.loginWithLinkedIn } + if (service === 'phone') { + return options.loginWithPhone + } + return false } diff --git a/src/Mutation/resendPhoneVerification.js b/src/Mutation/resendPhoneVerification.js new file mode 100644 index 0000000..d8dcfe8 --- /dev/null +++ b/src/Mutation/resendPhoneVerification.js @@ -0,0 +1,15 @@ +import callMethod from '../callMethod' +import {Meteor} from 'meteor/meteor' + + +export default async function (root, options, context) { + if (!options.phone) { + throw new Error('Phone number is required') + } + + callMethod(context, 'requestPhoneVerification', options.phone) + + return { + success: true + } +} diff --git a/src/Mutations.js b/src/Mutations.js index 137393a..fd56a19 100644 --- a/src/Mutations.js +++ b/src/Mutations.js @@ -23,6 +23,20 @@ export default function(options) { }`); } + if (hasService(options, 'phone')) { + mutations.push(` + type Mutation { + # Log the user in with a phone. + loginWithPhone (phone: String, verificationCode: String): LoginMethodResponse + + # Create a new user with a phone. + createUserWithPhone (phone: String, password: String, profile: CreateUserProfileInput): AuthSuccessResponse + + # Send verification code to phone. + resendPhoneVerification (phone: String): AuthSuccessResponse + }`) + } + mutations.push(` type Mutation { # Log the user out. diff --git a/src/index.js b/src/index.js index 161ceb8..b2f37cc 100644 --- a/src/index.js +++ b/src/index.js @@ -11,6 +11,7 @@ const initAccounts = function(givenOptions) { loginWithGoogle: false, loginWithLinkedIn: false, loginWithPassword: true, + loginWithPhone: false, overrideCreateUser: null, // createUser, args, context }; const options = {