tanutapi / dart_meteor

A Meteor DDP library for Dart/Flutter developers.
Other
37 stars 15 forks source link

loginWithGoogle #52

Closed ivriets closed 2 years ago

ivriets commented 2 years ago

I can't find any docs how to use loginWithGoogle, is it any tips to make this work, Thank you

tanutapi commented 2 years ago

Two parts have to be implemented.

  1. An extension to MeteorClient

  2. Backend (meteor side) to handle Google sign-in

  3. accounts_google.dart

    
    import 'package:dart_meteor/dart_meteor.dart';

extension AccountsGoogle on MeteorClient { Future loginWithGoogle( Map<String, String> authHeaders, ) async { return await login({ 'googleFlutter': { 'authHeaders': authHeaders, }, }); } }

In the UI together with `google_sign_in` package.

void signInWithGoogle(BuildContext context) async { setState(() { _isProcessing = true; }); try { var googleSignIn = GoogleSignIn( scopes: [ 'email', 'profile', ], ); var googleUser = await googleSignIn.signIn(); if (googleUser != null) { if ((await googleUser.authHeaders).isNotEmpty) { try { await meteor.loginWithGoogle(await googleUser.authHeaders); MeteorClientLoginResult result = await meteor.logoutOtherClients(); await secureTokenWithPinCode(result.token); } catch (err) { showErrorDialog(context, err); } } } } catch (err) { // ignore: avoid_print print(err); // ignore: avoid_print print('Possible SHA-1 was not set in the firebase project.'); } setState(() { _isProcessing = false; }); }


2. Here is some example of the meteor side code. Use it with care.

import { Accounts } from 'meteor/accounts-base'; import { Log } from 'meteor/logging';

const ERROR_MESSAGE = 'Something went wrong while doing sign-in with Google.';

const createUser = (userData) => { const { username, email } = userData; if (!username && !email) { throw new Meteor.Error(400, 'Need to set a username or email'); } const user = { services: {} }; return Accounts._createUserCheckingDuplicates({ user, username, email, options: userData, }); };

Accounts.registerLoginHandler('googleFlutter', function (options) { if (options.googleFlutter) { Log.info('Login with googleFlutter'); Log.info(this.connection); // console.log('Login with googleFlutter'); const { authHeaders } = options.googleFlutter;

if (!authHeaders) {
  Log.error('Login with googleFlutter but no authHeaders was provided!');
  throw new Meteor.Error(403, ERROR_MESSAGE);
}
const bearerToken = authHeaders.Authorization;

let res = null;
try {
  res = HTTP.get('https://people.googleapis.com/v1/people/me/', {
    headers: {
      Authorization: bearerToken,
    },
    params: {
      personFields: 'names,emailAddresses,photos',
    },
  });
} catch (err) {
  Log.error(JSON.stringify(err));
  throw new Meteor.Error(403, ERROR_MESSAGE);
}

if (res && res.statusCode === 200 && res.data) {
  let email;
  let firstName;
  let lastName;
  let profilePictureUrl;
  let profilePictureThumbnailUrl;
  let etag;
  let resourceName;

  if (
    res.data.emailAddresses &&
    res.data.emailAddresses instanceof Array &&
    res.data.emailAddresses.length > 0
  ) {
    email = res.data.emailAddresses[0].value;
  }
  if (
    res.data.names &&
    res.data.names instanceof Array &&
    res.data.names.length > 0
  ) {
    firstName = res.data.names[0].givenName;
    lastName = res.data.names[0].familyName;
  }
  if (
    res.data.photos &&
    res.data.photos instanceof Array &&
    res.data.photos.length > 0
  ) {
    profilePictureUrl = res.data.photos[0].url;
    if (profilePictureUrl) {
      profilePictureUrl = profilePictureUrl.replace('=s100', '');
    }
    profilePictureThumbnailUrl = res.data.photos[0].url;
  }

  if (res.data.resourceName) {
    resourceName = res.data.resourceName;
  }
  if (res.data.etag) {
    etag = res.data.etag;
  }

  // console.log(email, firstName, lastName, profilePictureUrl, profilePictureThumbnailUrl, resourceName, etag);

  if (email && firstName && lastName) {
    let user = Accounts._findUserByQuery(
      {
        email: email,
      },
      {
        fields: { emails: 1 },
      }
    );

    if (
      !user &&
      (options.userCreationDisabled ||
        Accounts._options.forbidClientAccountCreation)
    ) {
      Accounts._handleError('User not found');
    }

    if (!user) {
      Log.info(`Creating new user from Google account: ${email}`);
      const userId = createUser({
        email: email,
        profile: {
          prefix: {
            local: '',
            en: '',
          },
          firstName: {
            local: firstName,
            en: firstName,
          },
          lastName: {
            local: lastName,
            en: lastName,
          },
          profilePictureUrl: profilePictureUrl,
          profilePictureThumbnailUrl: profilePictureThumbnailUrl,
        },
      });
      user = Accounts._findUserByQuery(
        { id: userId },
        {
          fields: { emails: 1 },
        }
      );
    }

    if (!user) {
      Log.info(`User could not be created: ${email}`);
      Accounts._handleError('User could not be created');
    }

    Meteor.users.update(user._id, {
      $set: {
        'services.googleFlutter': {
          createdAt: new Date(),
          resourceName: resourceName,
          etag: etag,
        },
        'profile.profilePictureUrl': profilePictureUrl,
        'profile.profilePictureThumbnailUrl': profilePictureThumbnailUrl,
      },
    });

    try {
      Meteor.users.update(
        {
          _id: user._id,
          'emails.address': email,
        },
        {
          $set: {
            'emails.$.verified': true,
          },
        }
      );
    } catch (error) {
      Log.error(JSON.stringify(error));
      throw new Meteor.Error(403, ERROR_MESSAGE);
    }

    Log.info(`Login with googleFlutter successed for ${email}`);

    return {
      userId: user._id,
    };
  }
}

throw new Meteor.Error(403, ERROR_MESSAGE);

} });



The server-side code inspired by https://gist.github.com/wendellrocha/794b2154bb18ce2b81b21c5da79cc76e
ivriets commented 2 years ago

Thank you for response, I'll try to implemented to my apps,

steveng15 commented 2 years ago

I got an error on "await secureTokenWithPinCode(result.token);" is this default void from package? If yes which package or should I write own void? @tanutapi