* @param {Object} options
* @param {Object} options.authenticator
* @param {String[]} options.authenticator.authnEnabled
- * @param {Object} options.authenticator.secureAuthOnly
+ * @param {Number=} options.authenticator.inactiveSessionLifespanSeconds
+ * @param {Boolean} options.authenticator.secureAuthOnly
* @param {Object} options.dingus
* @param {Object} options.dingus.proxyPrefix
* @param {Object} options.dingus.selfBaseUrl
this.indieAuthCommunication = new IndieAuthCommunication(logger, options);
this.mysteryBox = new MysteryBox(logger, options);
- this.cookieLifespan = 60 * 60 * 24 * 32;
+ this.cookieLifespan = options.authenticator.inactiveSessionLifespanSeconds || 60 * 60 * 24 * 32;
}
const cookieParts = [
`${cookieName}=${secureSession}`,
'HttpOnly',
+ 'SameSite=Lax',
];
if (this.options.authenticator.secureAuthOnly) {
cookieParts.push('Secure');
return;
}
+ // Otherwise, carry on with IndieAuth handshake.
let me, session, authorizationEndpoint;
try {
me = new URL(ctx.parsedBody['me']);
if (this.options.authenticator.authnEnabled.includes('indieAuth')
&& me) {
const profile = await this.indieAuthCommunication.fetchProfile(me);
- if (!profile || !profile.authorizationEndpoint) {
+ if (!profile || !profile.metadata) {
this.logger.debug(_scope, 'failed to find any profile information at url', { ctx });
ctx.errors.push(`No profile information was found at '${me}'.`);
} else {
// fetch and parse me for 'authorization_endpoint' relation links
try {
- authorizationEndpoint = new URL(profile.authorizationEndpoint);
+ authorizationEndpoint = new URL(profile.metadata.authorizationEndpoint);
} catch (e) {
- ctx.errors.push(`Unable to understand the authorization endpoint ('${profile.authorizationEndpoint}') indicated by that profile ('${me}') as a URL.`);
+ ctx.errors.push(`Unable to understand the authorization endpoint ('${profile.metadata.authorizationEndpoint}') indicated by that profile ('${me}') as a URL.`);
+ }
+
+ if (profile.metadata.issuer) {
+ // Validate issuer
+ try {
+ const issuer = new URL(profile.metadata.issuer);
+ if (issuer.hash
+ || issuer.search
+ || issuer.protocol.toLowerCase() !== 'https:') { // stupid URL trailing colon thing
+ this.logger.debug(_scope, 'supplied issuer url invalid', { ctx });
+ ctx.errors.push('Authorization server provided an invalid issuer field.');
+ }
+ } catch (e) {
+ this.logger.debug(_scope, 'failed to parse supplied issuer url', { ctx });
+ ctx.errors.push('Authorization server provided an unparsable issuer field.');
+ }
+ } else {
+ this.logger.debug(_scope, 'no issuer in metadata, assuming legacy mode', { ctx });
+ // Strict 20220212 compliance would error here.
+ // ctx.errors.push('Authorization server did not provide issuer field, as required by current specification.');
}
}
if (authorizationEndpoint) {
const pkce = await IndieAuthCommunication.generatePKCE();
+
session = {
authorizationEndpoint: authorizationEndpoint.href,
state: ctx.requestId,
codeVerifier: pkce.codeVerifier,
me,
redirect,
+ issuer: profile.metadata.issuer,
};
+ // Update auth endpoint parameters
Object.entries({
'response_type': 'code',
'client_id': this.options.dingus.selfBaseUrl,
/**
* GET request for returning IndieAuth redirect.
+ * This currently only redeems a scope-less profile.
* @param {http.ServerResponse} res
* @param {Object} ctx
*/
}
// Validate unpacked session values
+ // ...
// Add any auth errors
if (ctx.queryParams['error']) {
ctx.errors.push('invalid code');
}
+ // check issuer
+ if (ctx.session.issuer) {
+ if (ctx.queryParams['iss'] !== ctx.session.issuer) {
+ this.logger.debug(_scope, 'issuer mismatch', { ctx });
+ ctx.errors.push('invalid issuer');
+ }
+ } else {
+ this.logger.debug(_scope, 'no issuer in metadata, assuming legacy mode', { ctx });
+ // Strict 20220212 compliance would error here. (Also earlier.)
+ // ctx.errors.push('invalid issuer');
+ }
+
let redeemProfileUrl;
try {
redeemProfileUrl = new URL(ctx.session.authorizationEndpoint);
const newProfileUrl = new URL(profile.me);
// Rediscover auth endpoint for the new returned profile.
const newProfile = await this.indieAuthCommunication.fetchProfile(newProfileUrl);
- if (newProfile.authorizationEndpoint !== ctx.session.authorizationEndpoint) {
+ if (newProfile.metadata.authorizationEndpoint !== ctx.session.authorizationEndpoint) {
this.logger.debug(_scope, 'mis-matched auth endpoints between provided me and canonical me', { ctx, profile, newProfile });
ctx.errors.push('canonical profile url provided by authorization endpoint is not handled by that endpoint, cannot continue');
} else {
}
-module.exports = SessionManager;
\ No newline at end of file
+module.exports = SessionManager;