You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
160 lines
4.6 KiB
JavaScript
160 lines
4.6 KiB
JavaScript
"use strict";
|
|
|
|
const Promise = require("bluebird");
|
|
const nanoid = require("nanoid");
|
|
const mapObj = require("map-obj");
|
|
const defaultValue = require("default-value");
|
|
|
|
const errors = require("./errors");
|
|
|
|
function createResponse(session) {
|
|
return {
|
|
session: session.id,
|
|
completed: session.completedStageKeys,
|
|
params: mapObj(session.stages, ([stageKey, stageData]) => {
|
|
return [stageKey, stageData.parameters];
|
|
}),
|
|
flows: session.flows.map((flow) => {
|
|
return { stages: flow.stages };
|
|
})
|
|
};
|
|
}
|
|
|
|
function createResult(session) {
|
|
return Promise.try(() => {
|
|
return createResponse(session);
|
|
}).then((response) => {
|
|
return {
|
|
completed: session.flows.some((flow) => {
|
|
return flow.stages.every((stage) => session.completedStageKeys.includes(stage));
|
|
}),
|
|
sessionData: response
|
|
};
|
|
});
|
|
}
|
|
|
|
// let stages = {
|
|
// "m.login.dummy": (_sessionId) => {
|
|
// return {
|
|
// parameters: {
|
|
// foo: "bar"
|
|
// },
|
|
// handler: () => {
|
|
// return { result: "completed" };
|
|
// }
|
|
// };
|
|
// }
|
|
// };
|
|
|
|
/* FIXME:
|
|
- Restrict to valid stages / stage combinations (flows)
|
|
*/
|
|
|
|
function mapObjAsync(object, handler) {
|
|
return Promise.map(Object.entries(object), ([key, value]) => {
|
|
return handler(key, value);
|
|
}).reduce((mappedObject, [key, value]) => {
|
|
mappedObject[key] = value;
|
|
return mappedObject;
|
|
}, {});
|
|
}
|
|
|
|
module.exports = function createUIATracker({ stages }) {
|
|
let sessions = new Map();
|
|
|
|
function getSession(sessionId) {
|
|
if (sessions.has(sessionId)) {
|
|
return sessions.get(sessionId);
|
|
} else {
|
|
throw new errors.BadRequest("Authentication session does not exist");
|
|
}
|
|
}
|
|
|
|
return {
|
|
createSession: function ({ flows }) {
|
|
return Promise.try(() => {
|
|
let sessionId = nanoid();
|
|
|
|
return mapObjAsync(stages, (stageKey, stageInitializer) => {
|
|
return Promise.try(() => {
|
|
return stageInitializer();
|
|
}).then((data) => {
|
|
return [stageKey, {
|
|
handler: data.handler,
|
|
parameters: defaultValue(data.parameters, {})
|
|
}];
|
|
});
|
|
}).then((initializedStages) => {
|
|
let sessionObject = {
|
|
id: sessionId,
|
|
completedStageKeys: [],
|
|
stages: initializedStages,
|
|
flows: flows
|
|
};
|
|
|
|
sessions.set(sessionId, sessionObject);
|
|
|
|
return createResponse(sessionObject);
|
|
});
|
|
});
|
|
},
|
|
performAuthentication: function ({ sessionId, type, data }) {
|
|
return Promise.try(() => {
|
|
let session = getSession(sessionId);
|
|
let stage = session.stages[type];
|
|
|
|
return Promise.try(() => {
|
|
if (stage != null) {
|
|
if (session.completedStageKeys.includes(type)) {
|
|
/* Stage was already completed */
|
|
/* FIXME: For now, we throw a 400 error; we may need to reevaluate this later: https://github.com/matrix-org/matrix-doc/issues/1987 */
|
|
throw new errors.BadRequest("Attempted to complete an already-completed authentication stage");
|
|
} else {
|
|
return Promise.try(() => {
|
|
let handler = stage.handler;
|
|
|
|
if (handler != null) {
|
|
return Promise.try(() => {
|
|
return handler(data);
|
|
}).catch(errors.AuthenticationError, (error) => {
|
|
/* This attaches information about the current state of the authentication process, to any authentication error that occurs, so that it ends up in the response - as is required by the specification, for retryable authentication errors. */
|
|
return Promise.try(() => {
|
|
return createResponse(session);
|
|
}).then((response) => {
|
|
error.errorMeta = response;
|
|
throw error;
|
|
});
|
|
});
|
|
} else {
|
|
throw new Error("Authentication stage is missing a handler; this is a bug");
|
|
}
|
|
}).then((handlerResult) => {
|
|
if (handlerResult.newParameters != null) {
|
|
Object.assign(stage.parameters, handlerResult.newParameters);
|
|
}
|
|
|
|
if (handlerResult.result === "completed") {
|
|
session.completedStageKeys.push(type);
|
|
} else if (handlerResult.result === "nextStepRequired") {
|
|
/* Do nothing, in this case new parameters will usually have been assigned */
|
|
} else if (handlerResult.result === "failed") {
|
|
/* FIXME: Send along metadata? */
|
|
throw new errors.Unauthorized(handlerResult.reason);
|
|
} else {
|
|
throw new Error(`Unrecognized authentication handler result '${handlerResult.result}'; this is a bug`);
|
|
}
|
|
|
|
return createResult(session);
|
|
});
|
|
}
|
|
} else {
|
|
throw new errors.BadRequest("Attempted to complete a non-existent authentication stage");
|
|
}
|
|
});
|
|
});
|
|
},
|
|
getStatus: function ({ sessionId }) {
|
|
return createResult(getSession(sessionId));
|
|
}
|
|
};
|
|
}; |