You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-29 16:43:09 +03:00
229 lines
6.8 KiB
JavaScript
229 lines
6.8 KiB
JavaScript
/*
|
|
Copyright 2016 OpenMarket Ltd
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
"use strict";
|
|
|
|
/** @module interactive-auth */
|
|
var q = require("q");
|
|
|
|
var utils = require("./utils");
|
|
|
|
/**
|
|
* Abstracts the logic used to drive the interactive auth process.
|
|
*
|
|
* <p>Components implementing an interactive auth flow should instantiate one of
|
|
* these, passing in the necessary callbacks to the constructor. They should
|
|
* then call attemptAuth, which will return a promise which will resolve or
|
|
* reject when the interactive-auth process completes.
|
|
*
|
|
* <p>Meanwhile, calls will be made to the startAuthStage and doRequest
|
|
* callbacks, and information gathered from the user can be submitted with
|
|
* submitAuthDict.
|
|
*
|
|
* @constructor
|
|
* @alias module:interactive-auth
|
|
*
|
|
* @param {object} opts options object
|
|
*
|
|
* @param {object?} opts.authData error response from the last request. If
|
|
* null, a request will be made with no auth before starting.
|
|
*
|
|
* @param {function(object?): module:client.Promise} opts.doRequest
|
|
* called with the new auth dict to submit the request. Should return a
|
|
* promise which resolves to the successful response or rejects with a
|
|
* MatrixError.
|
|
*
|
|
* @param {function(string, object?)} opts.startAuthStage
|
|
* called to ask the UI to start a particular auth stage. The arguments
|
|
* are: the login type (eg m.login.password); and (if the last request
|
|
* returned an error), an error object, with fields 'errcode' and 'error'.
|
|
*
|
|
*/
|
|
function InteractiveAuth(opts) {
|
|
this._data = opts.authData;
|
|
this._requestCallback = opts.doRequest;
|
|
this._startAuthStageCallback = opts.startAuthStage;
|
|
this._completionDeferred = null;
|
|
}
|
|
|
|
InteractiveAuth.prototype = {
|
|
/**
|
|
* begin the authentication process.
|
|
*
|
|
* @return {module:client.Promise} which resolves to the response on success,
|
|
* or rejects with the error on failure.
|
|
*/
|
|
attemptAuth: function() {
|
|
this._completionDeferred = q.defer();
|
|
|
|
if (!this._data) {
|
|
this._doRequest(null);
|
|
} else {
|
|
this._startNextAuthStage();
|
|
}
|
|
|
|
return this._completionDeferred.promise;
|
|
},
|
|
|
|
/**
|
|
* get the auth session ID
|
|
*
|
|
* @return {string} session id
|
|
*/
|
|
getSessionId: function() {
|
|
return this._data ? this._data.session : undefined;
|
|
},
|
|
|
|
/**
|
|
* get the server params for a given stage
|
|
*
|
|
* @param {string} login type for the stage
|
|
* @return {object?} any parameters from the server for this stage
|
|
*/
|
|
getStageParams: function(loginType) {
|
|
var params = {};
|
|
if (this._data && this._data.params) {
|
|
params = this._data.params;
|
|
}
|
|
return params[loginType];
|
|
},
|
|
|
|
/**
|
|
* submit a new auth dict and fire off the request. This will either
|
|
* make attemptAuth resolve/reject, or cause the startAuthStage callback
|
|
* to be called for a new stage.
|
|
*
|
|
* @param {object} authData new auth dict to send to the server. Should
|
|
* include a `type` propterty denoting the login type, as well as any
|
|
* other params for that stage.
|
|
*/
|
|
submitAuthDict: function(authData) {
|
|
if (!this._completionDeferred) {
|
|
throw new Error("submitAuthDict() called before attemptAuth()");
|
|
}
|
|
|
|
// use the sessionid from the last request.
|
|
var auth = {
|
|
session: this._data.session,
|
|
};
|
|
utils.extend(auth, authData);
|
|
|
|
this._doRequest(auth);
|
|
},
|
|
|
|
/**
|
|
* Fire off a request, and either resolve the promise, or call
|
|
* startAuthStage.
|
|
*
|
|
* @private
|
|
* @param {object?} auth new auth dict, including session id
|
|
*/
|
|
_doRequest: function(auth) {
|
|
var self = this;
|
|
|
|
// hackery to make sure that synchronous exceptions end up in the catch
|
|
// handler (without the additional event loop entailed by q.fcall or an
|
|
// extra q().then)
|
|
var prom;
|
|
try {
|
|
prom = this._requestCallback(auth);
|
|
} catch (e) {
|
|
prom = q.reject(e);
|
|
}
|
|
|
|
prom.then(
|
|
function(result) {
|
|
console.log("result from request: ", result);
|
|
self._completionDeferred.resolve(result);
|
|
}, function(error) {
|
|
if (error.httpStatus !== 401 || !error.data || !error.data.flows) {
|
|
// doesn't look like an interactive-auth failure. fail the whole lot.
|
|
throw error;
|
|
}
|
|
self._data = error.data;
|
|
self._startNextAuthStage();
|
|
}
|
|
).catch(this._completionDeferred.reject).done();
|
|
},
|
|
|
|
/**
|
|
* Pick the next stage and call the callback
|
|
*
|
|
* @private
|
|
*/
|
|
_startNextAuthStage: function() {
|
|
var nextStage = this._chooseStage();
|
|
if (!nextStage) {
|
|
throw new Error("No incomplete flows from the server");
|
|
}
|
|
|
|
var stageError = null;
|
|
if (this._data.errcode || this._data.error) {
|
|
stageError = {
|
|
errcode: this._data.errcode || "",
|
|
error: this._data.error || "",
|
|
};
|
|
}
|
|
this._startAuthStageCallback(nextStage, stageError);
|
|
},
|
|
|
|
/**
|
|
* Pick the next auth stage
|
|
*
|
|
* @private
|
|
* @return {string?} login type
|
|
*/
|
|
_chooseStage: function() {
|
|
var flow = this._chooseFlow();
|
|
console.log("Active flow => %s", JSON.stringify(flow));
|
|
var nextStage = this._firstUncompletedStage(flow);
|
|
console.log("Next stage: %s", nextStage);
|
|
return nextStage;
|
|
},
|
|
|
|
/**
|
|
* Pick one of the flows from the returned list
|
|
*
|
|
* @private
|
|
* @return {object} flow
|
|
*/
|
|
_chooseFlow: function() {
|
|
var flows = this._data.flows || [];
|
|
// always use the first flow for now
|
|
return flows[0];
|
|
},
|
|
|
|
/**
|
|
* Get the first uncompleted stage in the given flow
|
|
*
|
|
* @private
|
|
* @param {object} flow
|
|
* @return {string} login type
|
|
*/
|
|
_firstUncompletedStage: function(flow) {
|
|
var completed = (this._data || {}).completed || [];
|
|
for (var i = 0; i < flow.stages.length; ++i) {
|
|
var stageType = flow.stages[i];
|
|
if (completed.indexOf(stageType) === -1) {
|
|
return stageType;
|
|
}
|
|
}
|
|
},
|
|
};
|
|
|
|
|
|
/** */
|
|
module.exports = InteractiveAuth;
|