Skip to content

Commit

Permalink
feat: separate adapter and authenticator, move pam authenticator to o…
Browse files Browse the repository at this point in the history
…wn package
  • Loading branch information
hperrin committed Aug 9, 2022
1 parent 85c984b commit c1fd955
Show file tree
Hide file tree
Showing 27 changed files with 9,014 additions and 244 deletions.
10 changes: 7 additions & 3 deletions packages/adapter-file-system/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,20 @@ The default export is the adapter, and it's also a named export "Adapter". Insta
import express from 'express';
import nepheleServer from 'nephele';
import FileSystemAdapter from '@nephele/adapter-file-system';
import ExampleAuthenticator from '@nephele/authenticator-example';

const app = express();
const port = 8080;

app.use(
'/',
nepheleServer(
new FileSystemAdapter({
root: '/path/to/webdav/root',
}),
{
adapter: new FileSystemAdapter({
root: '/path/to/webdav/root',
}),
authenticator: new ExampleAuthenticator(),
},
{ realm: 'My WebDAV Server' }
)
);
Expand Down
3 changes: 0 additions & 3 deletions packages/adapter-file-system/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
"homepage": "https://github.com/sciactive/nephele#readme",
"devDependencies": {
"@tsconfig/recommended": "^1.0.1",
"@types/basic-auth": "^1.1.3",
"@types/express": "^4.17.13",
"@types/jest": "^27.5.0",
"@types/mmmagic": "^0.4.30",
Expand All @@ -44,8 +43,6 @@
"typescript": "^4.6.4"
},
"dependencies": {
"authenticate-pam": "^1.0.5",
"basic-auth": "^2.0.1",
"check-disk-space": "^3.3.1",
"mmmagic": "^0.5.3",
"nephele": "^1.0.0-alpha.2",
Expand Down
81 changes: 30 additions & 51 deletions packages/adapter-file-system/src/Adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import fsp from 'node:fs/promises';
import { constants } from 'node:fs';
import userid from 'userid';
import type { Request } from 'express';
import basicAuth from 'basic-auth';
import type {
Adapter as AdapterInterface,
AuthResponse as NepheleAuthResponse,
AuthResponse,
Method,
User,
} from 'nephele';
import {
BadGatewayError,
Expand All @@ -28,24 +28,16 @@ import {
otherWriteBit,
otherExecuteBit,
} from './FileSystemBits.js';
import User from './User.js';
import Resource from './Resource.js';

const { username, groupname } = userid;

export type AuthResponse = NepheleAuthResponse<any, { user: User }>;
const { username, groupname, uid, ids, gids } = userid;

export type AdapterConfig = {
/**
* The absolute path of the directory that acts as the root directory for the
* service.
*/
root?: string;
/**
* Whether PAM authentication should be used. Otherwise, the server will be
* completely open and any username/password will work.
*/
pam?: boolean;
/**
* The maximum filesize in megabytes to calculate etags by a CRC-32C checksum
* of the file contents. Anything above this file size will use a CRC-32C
Expand All @@ -66,22 +58,16 @@ export type AdapterConfig = {

/**
* Nephele file system adapter.
*
* Read the details on https://www.npmjs.com/package/authenticate-pam, which is
* required for PAM authentication.
*/
export default class Adapter implements AdapterInterface {
root: string;
pam: boolean;
contentEtagMaxMB: number;

constructor({
root = process.cwd(),
pam = true,
contentEtagMaxMB = 100,
}: AdapterConfig = {}) {
this.root = root.replace(/\/?$/, () => '/');
this.pam = pam;
this.contentEtagMaxMB = contentEtagMaxMB;

try {
Expand Down Expand Up @@ -114,19 +100,35 @@ export default class Adapter implements AdapterInterface {
}

async getUsername(uid: number): Promise<string> {
if (!this.pam) {
return 'nobody';
}

return username(uid);
}

async getGroupname(gid: number): Promise<string> {
if (!this.pam) {
return 'nobody';
return groupname(gid);
}

async getUid(user: User): Promise<number> {
if (!(await user.usernameMapsToSystemUser())) {
return -1;
}

return groupname(gid);
return uid(user.username);
}

async getGid(user: User): Promise<number> {
if (!(await user.usernameMapsToSystemUser())) {
return -1;
}

return ids(user.username).gid;
}

async getGids(user: User): Promise<number[]> {
if (!(await user.usernameMapsToSystemUser())) {
return [];
}

return gids(user.username);
}

async getComplianceClasses(
Expand Down Expand Up @@ -158,29 +160,6 @@ export default class Adapter implements AdapterInterface {
return 'max-age=604800';
}

async authenticate(request: Request, _response: AuthResponse) {
const authorization = request.get('Authorization');
let username = 'nobody';
let password = '';

if (authorization) {
const auth = basicAuth.parse(authorization);
if (auth) {
username = auth.name;
password = auth.pass;
}
}
const user = new User({ username, adapter: this });
await user.authenticate(password);

return user;
}

async cleanAuthentication(_request: Request, _response: AuthResponse) {
// Nothing is required for auth cleanup.
return;
}

async isAuthorized(url: URL, method: string, baseUrl: string, user: User) {
// What type of file access do we need?
let access = 'u';
Expand Down Expand Up @@ -213,8 +192,8 @@ export default class Adapter implements AdapterInterface {
}

// We need the user and group IDs.
const uid = await user.getUid();
const gids = await user.getGids();
const uid = await this.getUid(user);
const gids = await this.getGids(user);

// First make sure the server process and user has access to all
// directories in the tree.
Expand All @@ -237,7 +216,7 @@ export default class Adapter implements AdapterInterface {
exists = false;
}

if (this.pam) {
if (await user.usernameMapsToSystemUser()) {
for (let i = 1; i <= parts.length; i++) {
const ipathname = path.join('/', ...parts.slice(0, i));

Expand Down Expand Up @@ -293,7 +272,7 @@ export default class Adapter implements AdapterInterface {

// If we get to here, it means either the file exists and user has
// permission, or the file doesn't exist, and the user has access to all
// directories above it. (Or pam is disabled.)
// directories above it.
return true;
}

Expand Down
17 changes: 11 additions & 6 deletions packages/adapter-file-system/src/Lock.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { Lock as LockInterface } from 'nephele';

import Resource from './Resource.js';
import User from './User.js';

export default class Lock implements LockInterface {
resource: Resource;
Expand All @@ -12,11 +11,17 @@ export default class Lock implements LockInterface {
depth: '0' | 'infinity' = '0';
provisional: boolean = false;
owner: any = {};
user: User;

constructor({ resource, user }: { resource: Resource; user: User }) {
username: string;

constructor({
resource,
username,
}: {
resource: Resource;
username: string;
}) {
this.resource = resource;
this.user = user;
this.username = username;
}

async save() {
Expand All @@ -27,7 +32,7 @@ export default class Lock implements LockInterface {
}

meta.locks[this.token] = {
username: this.user.username,
username: this.username,
date: this.date.getTime(),
timeout: this.timeout,
scope: this.scope,
Expand Down

0 comments on commit c1fd955

Please sign in to comment.