FatFractal customer forums



Author Topic: Public extension  (Read 2916 times)

erka

  • Newbie
  • *
  • Posts: 29
    • View Profile
Public extension
« on: October 13, 2014, 11:02:37 AM »
Is it possible to call a extension without a logged in user?

We use Instagram as login in our app, the flow looks like this:
1. Login with instagram and receive access token.
2. Login/register to fatfractal with instagram username and access token as username/password.

The problem arise when the token expires and we need to change the password to the new token.

What we would like to do in case of expired token:
1. Client calls a "changePassword" extension with instagram username and access token as parameters.
2. Validate the access token on the server side and reset the password.
3. Retry login on the client side which will now be successful.

Our workaround would be to login with a temp user and change the password, then logout and login with the real user.


gkc

  • Administrator
  • *****
  • Posts: 375
    • View Profile
Re: Public extension
« Reply #1 on: October 13, 2014, 12:39:18 PM »
Sure is - you just need to declare the extension as "UNSECURED" - for example
create extension /someName UNSECURED as javascript:require(....)....

You may also find this post useful: https://forum.fatfractal.com/forum/index.php?topic=92.msg455#msg455

Also - I've asked our server-side Auth expert to see if there's anything we can do to integrate more directly with the login system, although what you are suggesting should work just fine

dave

  • Administrator
  • *****
  • Posts: 52
    • View Profile
Re: Public extension
« Reply #2 on: October 13, 2014, 03:27:23 PM »
Hi Erka,

We currently offer out-of-the-box support for Facebook and Twitter OAuth, but could add support for Instagram fairly easily if that is something that interests you. The functionality is described in detail here:

http://fatfractal.com/v2/documentation/#document-noserver-customize-api-oauth-authentication

as well as in this blog post:

http://fatfractal.com/v2/fyi-fatfractal-improves-authentication-support-with-full-oauth-1-0-and-oauth-2-0-support/

If that would be useful to you, let me know and I can add it.

Thanks,
Dave

erka

  • Newbie
  • *
  • Posts: 29
    • View Profile
Re: Public extension
« Reply #3 on: October 14, 2014, 05:08:15 AM »
Hi dave,

Instagram support would definitely be useful.

Thanks
Erik

dave

  • Administrator
  • *****
  • Posts: 52
    • View Profile
Re: Public extension
« Reply #4 on: October 14, 2014, 11:54:16 AM »
OK, I'll try to work that in this week.

erka

  • Newbie
  • *
  • Posts: 29
    • View Profile
Re: Public extension
« Reply #5 on: November 17, 2014, 06:54:06 AM »
Any updates on the Instagram support?

dave

  • Administrator
  • *****
  • Posts: 52
    • View Profile
Re: Public extension
« Reply #6 on: November 21, 2014, 02:45:57 PM »
Hi erka,

Sorry for the delay. I've put together backend support for Instagram, it should be available for you soon. For your app, you will need to modify Auth.js to handle Instagram, such as:

Code: [Select]
var require = require;
var exports = exports;

var auth = require('ffef/ScriptAuth');

var TWITTER = "TWITTER";
auth.setScribeApiClassName(TWITTER, "org.scribe.builder.api.TwitterApi$SSL");
auth.setScribeApiKey(TWITTER, "twitter_api_key");
auth.setScribeApiSecret(TWITTER, "twitter_api_secret");

var FACEBOOK = "FACEBOOK";
auth.setScribeApiClassName(FACEBOOK, "org.scribe.builder.api.FacebookApi");
auth.setScribeApiKey(FACEBOOK, "facebook_app_id");
auth.setScribeApiSecret(FACEBOOK, "facebook_app_secret");

var INSTAGRAM = "INSTAGRAM";
auth.setScribeApiClassName(INSTAGRAM, "org.scribe.builder.api.InstagramApi");
auth.setScribeApiKey(INSTAGRAM, "instagram_client_id");
auth.setScribeApiSecret(INSTAGRAM, "instagram_client_secret");

var AUTH_SERVICES = [TWITTER, FACEBOOK, INSTAGRAM];

function validAuthServices() {
    return AUTH_SERVICES;
}

function getRequestToken() {
    var authService = auth.getAuthRequestData().scriptAuthService;
    var callbackUri = auth.getAuthRequestData().callbackUri;

    var token = null;
    if (authService == TWITTER) {
        var scribeToken = auth.getScribeRequestToken(authService, callbackUri, true);   // this returns a Scribe Token object
        token = new auth.Token(scribeToken.getToken(), scribeToken.getSecret());        // this creates the type of object we should return
    }

    return token;
}

function getAuthorizationUri() {
    var authService = auth.getAuthRequestData().scriptAuthService;
    var requestToken = auth.getAuthRequestData().requestToken;
    var callbackUri = auth.getAuthRequestData().callbackUri;

    var scribeRequestToken = null;
    if (authService == TWITTER) {
        // 'requestToken' is an auth.Token type, must create a Scribe Token from it
        scribeRequestToken = new Packages.org.scribe.model.Token(requestToken.token, requestToken.secret);
    }

    return auth.getScribeAuthorizationUri(authService, scribeRequestToken, callbackUri, true);
}

function getVerifierParameterName() {
    var authService = auth.getAuthRequestData().scriptAuthService;
    switch (authService) {
        case TWITTER:
            return "oauth_verifier";
        case FACEBOOK:
        case INSTAGRAM:
            return "code";
        default:
            return null;
    }
}

function getAccessToken() {
    var authService = auth.getAuthRequestData().scriptAuthService;
    var requestToken = auth.getAuthRequestData().requestToken;
    var verifier = auth.getAuthRequestData().verifier;
    var callbackUri = auth.getAuthRequestData().callbackUri;

    // 'requestToken' is an auth.Token type, must create a Scribe Token from it
    var scribeRequestToken = new Packages.org.scribe.model.Token(requestToken.token, requestToken.secret);

    var scribeAccessToken = auth.getScribeAccessToken(authService, scribeRequestToken, verifier, callbackUri, true);
    var accessToken = new auth.Token(scribeAccessToken.getToken(), scribeAccessToken.getSecret());
    return accessToken;
}

function validateRegisterRequest() {
    var rr = auth.getAuthRequestData().registerRequest;
    var authService = rr.scriptAuthService;

    switch (authService) {
        case TWITTER:
            if (rr.token && rr.secret) {
                return true;
            } else {
                print("ERROR: Script auth service " + authService + " requires credential fields 'token' and 'secret'");
                return false;
            }
        case FACEBOOK:
        case INSTAGRAM:
            if (rr.token) {
                return true;
            } else {
                print("ERROR: Script auth service " + authService + " requires credential field 'token'");
                return false;
            }
        default:
            print("ERROR: Unknown script auth service: " + authService);
            return false;
    }
}

/**
 * Takes a credential map and removes any sensitive information, such as passwords
 * @return {*} Sanitized credential map
 */
function sanitizeCredential() {
    var credential = auth.getAuthRequestData().credential;
    var authService = credential.scriptAuthService;

    var sanitizedCredential = {};
    for (var i in credential) {
        sanitizedCredential[i] = credential[i];
    }

    switch (authService) {
        case TWITTER:
        case INSTAGRAM:
            delete sanitizedCredential.token;
            delete sanitizedCredential.secret;
            break;
        case FACEBOOK:
            delete sanitizedCredential.token;
            break;
    }

    return sanitizedCredential;
}

/**
 * Verify provided credentials.
 * If successful, return an {@link FFUser} object containing AT LEAST a username, more information if applicable.
 * If unsuccessful, return null.
 * Note that any additional information returned in the FFUser object will be used to supplement a registration request.
 * @return {*} FFUser object, or null if authentication fails.
 */
function verifyCredential() {
    var credential = auth.getAuthRequestData().credential;
    var createAccount = auth.getAuthRequestData().createAccount;
    var authService = credential.scriptAuthService;

    var scribeAccessToken = new Packages.org.scribe.model.Token(credential.token, credential.secret);
    var url = null;
    switch (authService) {
        case TWITTER:
            url = "https://api.twitter.com/1.1/account/verify_credentials.json";
            break;
        case FACEBOOK:
            url = "https://graph.facebook.com/me";
            break;
        case INSTAGRAM:
            url = "https://api.instagram.com/v1/users/self";
            break;
        default:
            throw new Error("Unknown ScriptAuth service: " + authService);
    }

    var rawUser = auth.scribeGet(authService, url, scribeAccessToken);

    // At this point, rawUser is just the raw response which may be an error response or the actual user response
    // check for errors
    // Twitter response has "errors"
    // Facebook response has "error"
    if ((authService === TWITTER && rawUser.errors) ||
        (authService === FACEBOOK && rawUser.error) ||
        (authService === INSTAGRAM && rawUser.meta.error_type)) {
        print("Error retrieving user object from service " + authService + " - response is " + JSON.stringify(rawUser));
        return null;
    }

    var userName, firstName, lastName, email;
    switch (authService) {
        case TWITTER:
            userName = rawUser.screen_name;
            email = null;  // not returned by Twitter API calls

            var n = names(rawUser.name);
            firstName = n[0];
            lastName = n[1];

            break;
        case FACEBOOK:
            userName = rawUser.username || rawUser.email;
            if (!userName) {
                throw ('ERROR: Facebook user has no username or email : ' + JSON.stringify(rawUser)).toString();
            }
            firstName = rawUser.first_name;
            lastName = rawUser.last_name;
            email = rawUser.email;
            break;
        case INSTAGRAM:
            userName = rawUser.data.username;
            email = null;

            n = names(rawUser.data.full_name);
            firstName = n[0];
            lastName = n[1];

            break;
    }

    return new auth.FFUser(userName, firstName, lastName, email);
}

exports.validAuthServices = validAuthServices;
exports.getRequestToken = getRequestToken;
exports.getAuthorizationUri = getAuthorizationUri;
exports.getVerifierParameterName = getVerifierParameterName;
exports.getAccessToken = getAccessToken;

exports.validateRegisterRequest = validateRegisterRequest;
exports.sanitizeCredential = sanitizeCredential;
exports.verifyCredential = verifyCredential;


// Utility

function names(fullName) {
    var names = fullName.split(" "),
        firstName, lastName;
    if (names.length > 1) {
        var tmp = names[0];
        for (var i = 1; i < names.length-1; i++) {
            tmp += " " + names[i];
        }
        firstName = tmp.toString(); // necessary to turn ConsString into normal string (Rhino-specific)
        lastName = names[names.length-1];
    } else {
        firstName = fullName;
        lastName = "";
    }
    return [firstName, lastName];
}

Finally, in your client code, you'll need to declare your Instagram callback URI, e.g. in Objective-C:

Code: [Select]
        [ff setCallbackUri:@"x-instagram-ff://authorize" forScriptAuthService:@"INSTAGRAM"];

In case you haven't used ScriptAuth before, check out http://fatfractal.com/v2/fyi-fatfractal-improves-authentication-support-with-full-oauth-1-0-and-oauth-2-0-support and the accompanying code at https://github.com/FatFractal/fyi.scriptauth.

Dave

gkc

  • Administrator
  • *****
  • Posts: 375
    • View Profile
Re: Public extension
« Reply #7 on: November 22, 2014, 01:19:20 AM »
FYI, This weekend's cloud release will include support for this

dev

  • Newbie
  • *
  • Posts: 15
    • View Profile
Re: Public extension
« Reply #8 on: November 24, 2014, 10:16:39 AM »
I can't get this to work.

I copy pasted the code to Auth.js with my instagram_client_id and my instagram_client_secret.

And I in my app:
Code: [Select]
[[FF main] setCallbackUri:@"myCallbackURI" forScriptAuthService:@"INSTAGRAM"];
And then by the looks of how use the Facebook and Twitter from your documentation, I'll try this:
Code: [Select]
[[FF main] retrieveAccessTokenForScriptAuthService:@"INSTAGRAM" callbackUriWithVerifier:@"myCallbackURI" onComplete:^(NSError *theErr, id theObj, NSHTTPURLResponse *theResponse) {
       
        if (theErr) {
            NSLog(@"INSTA ERROR %@", theErr.localizedDescription);
        }else{
            NSLog(@"INSTA SUCCESS %@", theObj);
        }
    }];

The app crashes at:
Code: [Select]
[FatFractal urlEncodeString:]
« Last Edit: November 24, 2014, 10:22:02 AM by dev »

dave

  • Administrator
  • *****
  • Posts: 52
    • View Profile
Re: Public extension
« Reply #9 on: November 25, 2014, 01:33:39 PM »
The retrieveAccessTokenForScriptAuthService:callbackUriWithVerifier:onComplete: method should be provided with the complete callback URI after returning from the Instagram login page, which should contain a code in the query. E.g., if your callback URI is:

x-instagram://authorize

then the callback URI with verifier should look something like this:

x-instagram://authorize?code=blah123

The code is the key piece of information needed, as it gets traded for the access token.

Dave


 

Copyright © FatFractal customer forums