Thanks Kevin. I took my project, stripped it down to bare bones and I'm still getting the error. I can't see how this could be related to something wonky that I'm doing in my project at this point, but I'm including code below. Maybe you can see a problem that I missed. (I can also give you a zip file containing the entire test project if that would be helpful.)
In case it's relevant, here's my application.ffdl file:
#
# Application configuration
#
SET ActivateUsersOnReg true
SET AllowAutoRegistration false
SET AllowNewCollections false
SET AllowNewObjectTypes false
SET AllowNewMembers false
SET AllowSystemUserToLogin true
SET SystemUserPassword <password redacted>
SET AndroidPushAuthToken YOUR_ANDROID_AUTH_TOKEN_GOES_HERE
SET ApplePushKeystorePassword YOUR_KEYSTORE_PASSWORD_GOES_HERE
SET ApplePushUsingProduction false
SET LogLevel INFO
SET PushIsInactive true
SET ETaggingEnabled true
SET CORS https://system.fatfractal.com
SET LogToDatabase true
SET PasswordValidation true
SET PasswordPattern (.+)
SET PasswordMessage Password cannot be empty
#
# FFUser
#
CREATE OBJECTTYPE FFUser (userName STRING, firstName STRING, lastName STRING, email STRING, active BOOLEAN, authDomain STRING, scriptAuthService STRING, groups GRABBAG /FFUserGroup, notif_ids GRABBAG /FFNotificationID)
CREATE ALIAS ON OBJECTTYPE FFUser GRABBAG BackReferences.FFUserGroup.users AS memberOfGroups
CREATE COLLECTION /FFUser OBJECTTYPE FFUser
#
# FFUserGroup
#
CREATE OBJECTTYPE FFUserGroup (groupName STRING, users GRABBAG /FFUser)
CREATE COLLECTION /FFUserGroup OBJECTTYPE FFUserGroup
#
# FFNotificationID
#
CREATE OBJECTTYPE FFNotificationID (idType STRING, idValue STRING)
CREATE COLLECTION /FFNotificationID OBJECTTYPE FFNotificationID
#
# PasswordResetToken
# An object-type and a collection to hold password reset tokens
# Only allow the system user, or members of the system.admins group, to create, retrieve, update or delete password reset tokens
#
CREATE OBJECTTYPE PasswordResetToken (userGuid STRING)
CREATE COLLECTION /PasswordResetToken OBJECTTYPE PasswordResetToken
PERMIT create:system.admins read:system.admins write:system.admins ON /PasswordResetToken
#
# Event Handlers
#
#
# Extension Resources
#
CREATE EXTENSION /changePassword AS javascript:require ('scripts/ChangePassword.js').changePassword();
CREATE EXTENSION /sendPasswordReset UNSECURED AS javascript:require ('scripts/PasswordReset.js').sendPasswordResetToken();
CREATE EXTENSION /resetPassword UNSECURED AS javascript:require ('scripts/PasswordReset.js').resetPassword();
Here is the ChangePassword.js script I'm using which is essentially the same as the one you posted:
var require = require;
var exports = exports;
var print = print;
var ff = require('ffef/FatFractal');
exports.changePassword= function() {
// Restrict execution of this code to only logged in users
if (ff.getActiveUser().guid == "anonymous") {
throw {statusCode:403, statusMessage:"Must be logged in."};
}
var data = ff.getExtensionRequestData();
var r = ff.response();
// check that the password has been supplied
var secret = data.httpContent['password'];
if (! secret || secret === null || secret == '') {
r.responseCode = "400";
r.statusMessage = "Empty password";
return;
}
// Change the password
try {
ff.resetPassword (ff.getActiveUser().guid, secret);
} catch (ex1) {
ff.logger.error("Change password failed for user " + ff.getActiveUser.userName + ": " + JSON.stringify(ex1));
r.responseCode = "500";
r.statusMessage = "Internal server error";
return;
}
// return statusCode 200 and an appropriate message
r.responseCode = "200";
r.statusMessage = "Password has been changed";
};
In my sample iOS project, the user enters a username and the current password, then taps a button. When the button is tapped, the changePasswordButtonTapped: method is called, which logs into FatFractal using the entered credentials. That method then calls the changePasswordTrial: method which attempts to repeatedly change the logged in user's password. These are the only two methods in the sample project that invoke anything FatFractal related.
#pragma mark - FatFractal Interactions
- (void)changePasswordTrial:(NSNumber *)trialNumber {
NSInteger trialInteger = [trialNumber integerValue];
if(trialInteger <= 3) {
NSString *password = [NSString stringWithFormat:@"password%d", trialInteger];
NSLog(@"Beginning Trial %d with password: %@", trialInteger, password);
NSDictionary *passwordDictionary = [NSDictionary dictionaryWithObject:password forKey:@"password"];
[self.fatFractal postObj:passwordDictionary toExtension:@"changePassword" onComplete:^(NSError *theErr, id theObj, NSHTTPURLResponse *theResponse) {
if(theErr == nil) {
// Success
NSLog(@"Trial %d Succeeded", trialInteger);
[self performSelector:@selector(changePasswordTrial:) withObject:[NSNumber numberWithInteger:(trialInteger + 1)] afterDelay:1.0f];
}
else {
// Failure
NSLog(@"Trial %d Failed with error: %@", trialInteger,[theErr.userInfo objectForKey:NSLocalizedDescriptionKey]);
}
}];
}
else {
NSLog(@"All trials completed");
}
}
#pragma mark - Interface Actions
- (void)changePasswordButtonTapped:(id)sender {
self.fatFractal = [[FatFractal alloc] initWithBaseUrl:@"https://metakite.fatfractal.com/benjamin"];
[self.fatFractal loginWithUserName:self.usernameTextField.text andPassword:self.passwordTextField.text onComplete:^(NSError *theErr, id theObj, NSHTTPURLResponse *theResponse) {
if(theErr == nil) {
// Success
[self performSelector:@selector(changePasswordTrial:) withObject:[NSNumber numberWithInteger:1] afterDelay:1.0f];
}
else {
// Failure
NSLog(@"Cannot log in with error: %@", [theErr.userInfo objectForKey:NSLocalizedDescriptionKey]);
}
}];
}
When I run this code, I get these messages printed to the console:
2014-03-06 15:11:06.414 Benjamin[33842:70b] invokeHttpMethod: Will send HTTP POST to url: https://metakite.fatfractal.com/benjamin/ff/login
2014-03-06 15:11:06.545 Benjamin[33842:70b] canAuthenticateAgainstProtectionSpace <NSURLProtectionSpace: 0xc58cf50>: Host:metakite.fatfractal.com, Server:https, Auth-Scheme:NSURLAuthenticationMethodServerTrust, Realm:(null), Port:443, Proxy:NO, Proxy-Type:(null) called
2014-03-06 15:11:07.712 Benjamin[33842:70b] Beginning Trial 1 with password: password1
2014-03-06 15:11:07.712 Benjamin[33842:70b] invokeHttpMethod: Will send HTTP POST to url: https://metakite.fatfractal.com/benjamin/ff/ext/changePassword
2014-03-06 15:11:07.870 Benjamin[33842:70b] Trial 1 Succeeded
2014-03-06 15:11:08.871 Benjamin[33842:70b] Beginning Trial 2 with password: password2
2014-03-06 15:11:08.871 Benjamin[33842:70b] invokeHttpMethod: Will send HTTP POST to url: https://metakite.fatfractal.com/benjamin/ff/ext/changePassword
2014-03-06 15:11:09.021 Benjamin[33842:70b] HTTP POST failed with response code 401
Status message from backend is Your session has expired; please re-login
2014-03-06 15:11:09.021 Benjamin[33842:70b] Trial 2 Failed with error: HTTP POST failed with response code 401
Status message from backend is Your session has expired; please re-login
I also get these messages logged to my FatFractal server logs:
INFO - com.fatfractal.noserver.actions.AbstractAction - 2014-03-06T20:10:57.292Z - /ff/login - NoUser - Thread[pool-3-thread-23,5,main] - REQUEST: METHOD: POST, HEADERS: {X-Forwarded-Protocol=https, Accept-Language=en-us, Cookie=sessionId=(null); userGuid=(null), Host=metakite.fatfractal.com, Content-Length=108, Accept-Encoding=gzip, deflate, X-Real-IP=98.220.168.122, X-Forwarded-For=98.220.168.122, User-Agent=Benjamin/2.0.4 CFNetwork/672.0.8 Darwin/13.1.0, Connection=close, Content-Type=application/json, Accept=*/*}, PARAMS: {}, BODY: {"credential":{"authDomain":"LOCAL","userName":"test","password":"########"}}
INFO - com.fatfractal.noserver.actions.AbstractAction - 2014-03-06T20:10:57.310Z - /ff/login - NoUser - Thread[pool-3-thread-23,5,main] - RESPONSE : METHOD: POST, HEADERS: [Content-Type=application/json, Set-Cookie=userGuid=c4KvxgHG6obdfypIuI6Xf4; max-age=129600; expires=8 Mar 2014 08:10:57 GMT; domain=metakite.fatfractal.com; path=/benjamin/, Set-Cookie=sessionId=7C0TOZR-qk4ItelkvV4Cl7; max-age=129600; expires=8 Mar 2014 08:10:57 GMT; domain=metakite.fatfractal.com; path=/benjamin/], CODE: 200
INFO - com.fatfractal.noserver.actions.AbstractAction - 2014-03-06T20:10:58.386Z - /ff/ext/changePassword - NoUser - Thread[pool-3-thread-13,5,main] - REQUEST: METHOD: POST, HEADERS: {X-Forwarded-Protocol=https, Accept-Language=en-us, Cookie=sessionId=7C0TOZR-qk4ItelkvV4Cl7; userGuid=c4KvxgHG6obdfypIuI6Xf4, Host=metakite.fatfractal.com, Content-Length=30, Accept-Encoding=gzip, deflate, X-Real-IP=98.220.168.122, X-Forwarded-For=98.220.168.122, User-Agent=Benjamin/2.0.4 CFNetwork/672.0.8 Darwin/13.1.0, Connection=close, Content-Type=application/json, Accept=*/*}, PARAMS: {}, BODY: {"password":"########"}
INFO - com.fatfractal.noserver.actions.Extension - 2014-03-06T20:10:58.392Z - /ff/ext/changePassword - test - Thread[pool-3-thread-13,5,main] - Executing script require ('scripts/ChangePassword.js').changePassword();
INFO - com.fatfractal.noserver.actions.AbstractAction - 2014-03-06T20:10:58.415Z - /ff/ext/changePassword - test - Thread[pool-3-thread-13,5,main] - RESPONSE : METHOD: POST, HEADERS: [Content-Type=application/json], CODE: 200
INFO - com.fatfractal.noserver.actions.AbstractAction - 2014-03-06T20:10:59.609Z - /ff/ext/changePassword - NoUser - Thread[pool-3-thread-10,5,main] - REQUEST: METHOD: POST, HEADERS: {X-Forwarded-Protocol=https, Accept-Language=en-us, Cookie=sessionId=7C0TOZR-qk4ItelkvV4Cl7; userGuid=c4KvxgHG6obdfypIuI6Xf4, Host=metakite.fatfractal.com, Content-Length=30, Accept-Encoding=gzip, deflate, X-Real-IP=98.220.168.122, X-Forwarded-For=98.220.168.122, User-Agent=Benjamin/2.0.4 CFNetwork/672.0.8 Darwin/13.1.0, Connection=close, Content-Type=application/json, Accept=*/*}, PARAMS: {}, BODY: {"password":"########"}
INFO - com.fatfractal.noserver.security.AppManagerUsersAndGroups - 2014-03-06T20:10:59.611Z - /ff/ext/changePassword - NoUser - Thread[pool-3-thread-10,5,main] - sessionId 7C0TOZR-qk4ItelkvV4Cl7 not current for test - invalidating cache and trying again
INFO - com.fatfractal.noserver.security.AppManagerUsersAndGroups - 2014-03-06T20:10:59.619Z - /ff/ext/changePassword - NoUser - Thread[pool-3-thread-10,5,main] - after cache invalidation, sessionId 7C0TOZR-qk4ItelkvV4Cl7 is STILL NOT current
WARN - com.fatfractal.noserver.actions.AbstractAction - 2014-03-06T20:10:59.620Z - /ff/ext/changePassword - NoUser - Thread[pool-3-thread-10,5,main] - com.fatfractal.noserver.exceptions.FFHttpExceptionUnauthorized: Your session has expired; please re-login
INFO - com.fatfractal.noserver.actions.AbstractAction - 2014-03-06T20:10:59.621Z - /ff/ext/changePassword - NoUser - Thread[pool-3-thread-10,5,main] - RESPONSE : METHOD: POST, HEADERS: [Content-Type=application/json, Set-Cookie=userGuid=deleted; max-age=0; expires=6 Mar 2014 20:10:59 GMT; domain=metakite.fatfractal.com; path=/benjamin/, Set-Cookie=sessionId=deleted; max-age=0; expires=6 Mar 2014 20:10:59 GMT; domain=metakite.fatfractal.com; path=/benjamin/], CODE: 401
Like I said, it seems like a pretty vanilla use of the FatFractal SDK, so I'm a bit confused why this should fail. Thanks for taking a look.