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

"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));
}
};
};