diff --git a/docs/sync.js b/docs/sync.js index 290b9d5b..ff4dbf26 100644 --- a/docs/sync.js +++ b/docs/sync.js @@ -242,7 +242,7 @@ class User { * @return {Promise} Returns a promise with a user */ static authenticate(server, provider, options) {} - + /** * Register/login a sync user using an external login provider. * @param {string} server - authentication server @@ -312,11 +312,16 @@ class User { get token() {} /** - * Returns true if this user is an administrator + * Returns true if this user is an administrator. * @type {bool} */ get isAdmin() {} + /** + * Returns true if the token is an administrator token. + */ + get isAdminToken() {} + /** * Logs out the user from the Realm Object Server. */ diff --git a/lib/index.d.ts b/lib/index.d.ts index 7f4ecf37..746f84ad 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -145,9 +145,9 @@ declare namespace Realm { */ isValid(): boolean; - min(property?: string): number|Date|null; - max(property?: string): number|Date|null; - sum(property?: string): number|null; + min(property?: string): number | Date | null; + max(property?: string): number | Date | null; + sum(property?: string): number | null; avg(property?: string): number; /** @@ -268,6 +268,7 @@ declare namespace Realm.Sync { static readonly current: User; readonly identity: string; readonly isAdmin: boolean; + readonly isAdminToken: boolean; readonly server: string; readonly token: string; static adminUser(adminToken: string, server?: string): User; @@ -349,9 +350,9 @@ declare namespace Realm.Sync { interface SyncError { name: string; - message: string; + message: string; isFatal: boolean; - category?: string; + category?: string; code: number; } diff --git a/lib/user-methods.js b/lib/user-methods.js index 09bc5ecf..3cc6ca61 100644 --- a/lib/user-methods.js +++ b/lib/user-methods.js @@ -46,11 +46,8 @@ const postHeaders = { 'accept': 'application/json' }; -function auth_url(server) { - if (server.charAt(server.length - 1) != '/') { - return server + '/auth'; - } - return server + 'auth'; +function append_url(server, path) { + return server + (server.charAt(server.length - 1) != '/' ? '/' : '') + path; } function scheduleAccessTokenRefresh(user, localRealmPath, realmUrl, expirationDate) { @@ -63,13 +60,63 @@ function print_error() { (console.error || console.log).apply(console, arguments); } +function validateRefresh(user, localRealmPath, response, json) { + let session = user._sessionForOnDiskPath(localRealmPath); + if (!session) { + print_error(`Unhandled session token refresh error: could not look up session at path ${localRealmPath}`); + return; + } + + const errorHandler = session.config.error; + if (response.status != 200) { + let error = new AuthError(json); + if (errorHandler) { + errorHandler(session, error); + } else { + print_error('Unhandled session token refresh error', error); + } + return; + } + if (session.state === 'invalid') { + return; + } + return session; +} + +function refreshAdminToken(user, localRealmPath, realmUrl) { + const token = user.token; + const server = user.server; + + // We don't need to actually refresh the token, but we need to let ROS know + // we're accessing the file and get the sync label for multiplexing + let parsedRealmUrl = url_parse(realmUrl); + const url = append_url(user.server, 'realms/files/' + encodeURIComponent(parsedRealmUrl.pathname)); + performFetch(url, {method: 'GET', timeout: 10000.0, headers: {Authorization: user.token}}) + .then((response) => response.json().then((json) => { return { response, json }; })) + .then((responseAndJson) => { + const response = responseAndJson.response; + const json = responseAndJson.json; + + const newUser = user.constructor.adminUser(token, server); + const session = validateRefresh(newUser, localRealmPath, response, json); + if (session) { + parsedRealmUrl.set('pathname', json.path); + session._refreshAccessToken(user.token, parsedRealmUrl.href, json.syncLabel); + } + }); +} + function refreshAccessToken(user, localRealmPath, realmUrl) { if (!user.server) { throw new Error("Server for user must be specified"); } let parsedRealmUrl = url_parse(realmUrl); - const url = auth_url(user.server); + if (user.isAdminToken) { + return refreshAdminToken(user, localRealmPath, realmUrl); + } + + const url = append_url(user.server, 'auth'); const options = { method: 'POST', body: JSON.stringify({ @@ -91,26 +138,13 @@ function refreshAccessToken(user, localRealmPath, realmUrl) { // Look up a fresh instance of the user. // We do this because in React Native Remote Debugging // `Realm.clearTestState()` will have invalidated the user object - let newUser = user.constructor.all[user.identity]; + const newUser = user.constructor.all[user.identity]; if (!newUser) { return; } - let session = newUser._sessionForOnDiskPath(localRealmPath); - if (!session) { - print_error(`Unhandled session token refresh error: could not look up session at path ${localRealmPath}`); - } - const errorHandler = session.config.error; - if (response.status != 200) { - let error = new AuthError(json); - if (errorHandler) { - errorHandler(session, error); - } else { - print_error('Unhandled session token refresh error', error); - } - return; - } - if (session.state === 'invalid') { + const session = validateRefresh(newUser, localRealmPath, response, json); + if (!session) { return; } @@ -119,6 +153,7 @@ function refreshAccessToken(user, localRealmPath, realmUrl) { parsedRealmUrl.set('pathname', tokenData.path); session._refreshAccessToken(json.access_token.token, parsedRealmUrl.href, tokenData.sync_label); + const errorHandler = session.config.error; if (errorHandler && errorHandler._notifyOnAccessTokenRefreshed) { errorHandler(session, errorHandler._notifyOnAccessTokenRefreshed) } @@ -144,7 +179,7 @@ function refreshAccessToken(user, localRealmPath, realmUrl) { */ function _authenticate(userConstructor, server, json, callback) { json.app_id = ''; - const url = auth_url(server); + const url = append_url(server, 'auth'); const options = { method: 'POST', body: JSON.stringify(json), diff --git a/src/js_sync.hpp b/src/js_sync.hpp index d373d863..13951839 100644 --- a/src/js_sync.hpp +++ b/src/js_sync.hpp @@ -69,12 +69,14 @@ public: static void get_identity(ContextType, ObjectType, ReturnValue &); static void get_token(ContextType, ObjectType, ReturnValue &); static void is_admin(ContextType, ObjectType, ReturnValue &); + static void is_admin_token(ContextType, ObjectType, ReturnValue &); PropertyMap const properties = { {"server", {wrap, nullptr}}, {"identity", {wrap, nullptr}}, {"token", {wrap, nullptr}}, {"isAdmin", {wrap, nullptr}}, + {"isAdminToken", {wrap, nullptr}}, }; static void create_user(ContextType, FunctionType, ObjectType, size_t, const ValueType[], ReturnValue &); @@ -125,6 +127,11 @@ void UserClass::is_admin(ContextType ctx, ObjectType object, ReturnValue &ret return_value.set(get_internal>(object)->get()->is_admin()); } +template +void UserClass::is_admin_token(ContextType ctx, ObjectType object, ReturnValue &return_value) { + return_value.set(get_internal>(object)->get()->token_type() == SyncUser::TokenType::Admin); +} + template void UserClass::create_user(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) { validate_argument_count(argc, 3, 5); diff --git a/src/object-store b/src/object-store index 21e671a1..8517ee7f 160000 --- a/src/object-store +++ b/src/object-store @@ -1 +1 @@ -Subproject commit 21e671a155989f4e34e279fc8a7ace5817495de0 +Subproject commit 8517ee7f4378fe0f54945b3e4973766ff65e455d