@nephele/authenticator-pam
Version:
PAM based authenticator (local system users) for the Nephele WebDAV server.
118 lines (103 loc) • 3.37 kB
text/typescript
import type { Request } from 'express';
import basicAuth from 'basic-auth';
import type {
Authenticator as AuthenticatorInterface,
AuthResponse as NepheleAuthResponse,
} from 'nephele';
import { UnauthorizedError } from 'nephele';
import User from './User.js';
export type AuthenticatorConfig = {
/**
* The realm is the name reported by the server when the user is prompted to
* authenticate.
*
* It should be HTTP header safe (shouldn't include double quotes or
* semicolon).
*/
realm?: string;
/**
* Allow the user to proceed, even if they are not authenticated.
*
* The authenticator will advertise that authentication is available, but the
* user will have access to the server without providing authentication.
*
* In the unauthorized state, the `user` presented to the Nephele adapter will
* have the username "nobody".
*
* WARNING: It is very dangerous to allow unauthorized access if write actions
* are allowed!
*/
unauthorizedAccess?: boolean;
/**
* Comma separated UID ranges that are allowed to log in.
*
* You can set, for example, "0,1000-1999" to allow the first 1000 normal
* users and root to log in.
*
* Root is always UID 0. On most systems, daemon users are assigned UIDs in
* the range 2-999, normal users are assigned UIDs in the range 1000-65533,
* and the "nobody" user is assigned UID 65534. On some systems (including
* macOS), normal users are assigned IDs starting at 500, which is why the
* default includes this range.
*/
allowedUIDs?: string;
};
export type AuthResponse = NepheleAuthResponse<any, { user: User }>;
/**
* Nephele PAM authenticator.
*
* Read the details on https://www.npmjs.com/package/authenticate-pam, which is
* required for PAM authentication.
*/
export default class Authenticator implements AuthenticatorInterface {
realm: string;
unauthorizedAccess: boolean;
allowedUIDs: string[];
constructor({
realm = 'Nephele WebDAV Service',
unauthorizedAccess = false,
allowedUIDs = '500-59999',
}: AuthenticatorConfig = {}) {
this.realm = realm;
this.unauthorizedAccess = unauthorizedAccess;
this.allowedUIDs = allowedUIDs.split(',').map((range) => range.trim());
}
async authenticate(request: Request, response: AuthResponse) {
const authorization = request.get('Authorization');
let username = '';
let password = '';
if (authorization) {
const auth = basicAuth.parse(authorization);
if (auth) {
username = auth.name;
password = auth.pass;
}
}
try {
if (username.trim() === '') {
throw new UnauthorizedError(
'Authentication is required to use this server.',
);
}
const user = new User({ username });
await user.authenticate(password, request.ip);
await user.checkUID(this.allowedUIDs);
return user;
} catch (e: any) {
if (e instanceof UnauthorizedError) {
response.set(
'WWW-Authenticate',
`Basic realm="${this.realm}", charset="UTF-8"`,
);
}
if (this.unauthorizedAccess) {
return new User({ username: 'nobody' });
}
throw e;
}
}
async cleanAuthentication(_request: Request, _response: AuthResponse) {
// Nothing is required for auth cleanup.
return;
}
}