Skip to content

Commit

Permalink
feat: add ability to tell tilmeld to log in a specific user during au…
Browse files Browse the repository at this point in the history
…thentication
  • Loading branch information
hperrin committed Mar 31, 2023
1 parent 599ee3d commit 922e145
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 24 deletions.
74 changes: 73 additions & 1 deletion packages/server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ app.listen(80);

You will need to import any entities you use on the server, so they are available to Nymph.

Now you can configure your client using your server's address (and the optional path, if set).
Now you can configure your **client**, using your server's address (and the optional path, if set).

```ts
import { Nymph } from '@nymphjs/client';
Expand All @@ -61,6 +61,78 @@ const nymph = new Nymph({
const MyEntity = nymph.addEntityClass(MyEntityClass);
```

The REST server will authenticate for you using the Tilmeld auth cookie and XSRF token, but if you need to, you can authenticate in some other way (like OAuth2), and place the user in `response.locals.user`.

```ts
import express from 'express';
import { Nymph } from '@nymphjs/nymph';
import SQLite3Driver from '@nymphjs/driver-sqlite3';
import { Tilmeld } from '@nymphjs/tilmeld';
import createServer from '@nymphjs/server';
import setup from '@nymphjs/tilmeld-setup';

// Import all the entities you will be using on the server.
import MyEntityClass from './entities/MyEntity';

// Consfigure Tilmeld.
const tilmeld = new Tilmeld({
appName: 'My Awesome App',
appUrl: 'https://mydomain.tld',
setupPath: '/user',
});

// Configure Nymph.
const nymph = new Nymph(
{},
new SQLite3Driver({
filename: ':memory:',
}),
tilmeld
);
const MyEntity = nymph.addEntityClass(MyEntityClass);

// Create your Express app.
const app = express();

// Authenticate the user manually.
app.use('/rest', async (request, response, next) => {
const { User } = tilmeld;

try {
// Somehow authenticate the user...
const user = await User.factoryUsername(username);

if (user.guid != null && user.enabled) {
response.locals.user = user;
}

next();
} catch (e: any) {
response.status(500);
response.send('Internal server error.');
}
});

// Create and use the REST server (with an optional path).
app.use('/rest', createServer(nymph));

// Create Tilmeld setup app.
app.user(
'/user',
setup(
{
restUrl: 'https://mydomain.tld/rest',
},
nymph
)
);

// Do anything else you need to do...

// Start your server.
app.listen(80);
```

# License

Copyright 2021 SciActive Inc
Expand Down
3 changes: 2 additions & 1 deletion packages/tilmeld-setup/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ const app = express();

// Use the REST server.
app.use('/rest', createServer(nymph));
// Use the Tilmeld Setup App, passing in the Nymph Client Config.

// Create Tilmeld setup app.
app.use(
'/user',
setup(
Expand Down
88 changes: 66 additions & 22 deletions packages/tilmeld/src/Tilmeld.ts
Original file line number Diff line number Diff line change
Expand Up @@ -960,6 +960,15 @@ export default class Tilmeld implements TilmeldInterface {
/**
* Check for a TILMELDAUTH token, and, if set, authenticate from it.
*
* You can also call this function after setting `response.locals.user` to the
* user you want to authenticate. You *should* check for `user.enabled` before
* setting this variable, unless you explicitly want to log in as a disabled
* user. (The user must be an instance of the User class for this Tilmeld
* instance.)
*
* This function will set `response.locals.user` to the logged in user on
* successful authentication.
*
* @param skipXsrfToken Skip the XSRF token check.
* @returns True if a user was authenticated, false on any failure.
*/
Expand All @@ -968,6 +977,17 @@ export default class Tilmeld implements TilmeldInterface {
return false;
}

if (
this.response &&
this.response.locals.user &&
this.response.locals.user instanceof this.User
) {
// The user has already been authenticated through some other means.
const user = this.response.locals.user;
this.fillSession(user);
return true;
}

const cookies = this.request.cookies ?? {};

// If a client does't support cookies, they can use the X-TILMELDAUTH header
Expand Down Expand Up @@ -1022,33 +1042,52 @@ export default class Tilmeld implements TilmeldInterface {
} else {
this.fillSession(user);
}

if (this.response) {
this.response.locals.user = user;
}

return true;
}

/**
* Logs the given user into the system.
*
* @param user The user.
* @param sendAuthHeader When true, a custom header with the auth token will be sent.
* @param sendAuthHeader Send the auth token as a custom header.
* @param sendCookie Send the auth token as a cookie.
* @returns True on success, false on failure.
*/
public login(user: User & UserData, sendAuthHeader: boolean) {
public login(
user: User & UserData,
sendAuthHeader: boolean,
sendCookie = true
) {
if (user.guid != null && user.enabled) {
if (this.response && !this.response.headersSent) {
const token = this.config.jwtBuilder(this.config, user);
const appUrl = new URL(this.config.appUrl);
this.response.cookie('TILMELDAUTH', token, {
domain: this.config.cookieDomain,
path: this.config.cookiePath,
maxAge: this.config.jwtExpire * 1000,
secure: appUrl.protocol === 'https',
httpOnly: false, // Allow JS access (for CSRF protection).
sameSite: appUrl.protocol === 'https' ? 'lax' : 'strict',
});
if (sendAuthHeader) {
this.response.set('X-TILMELDAUTH', token);
if (this.response) {
if (!this.response.headersSent) {
const token = this.config.jwtBuilder(this.config, user);

if (sendCookie) {
const appUrl = new URL(this.config.appUrl);
this.response.cookie('TILMELDAUTH', token, {
domain: this.config.cookieDomain,
path: this.config.cookiePath,
maxAge: this.config.jwtExpire * 1000,
secure: appUrl.protocol === 'https',
httpOnly: false, // Allow JS access (for CSRF protection).
sameSite: appUrl.protocol === 'https' ? 'lax' : 'strict',
});
}

if (sendAuthHeader) {
this.response.set('X-TILMELDAUTH', token);
}
}

this.response.locals.user = user;
}

this.fillSession(user);
return true;
}
Expand All @@ -1057,15 +1096,20 @@ export default class Tilmeld implements TilmeldInterface {

/**
* Logs the current user out of the system.
*
* @param clearCookie Clear the auth cookie. (Also send a header.)
*/
public logout() {
public logout(clearCookie = true) {
this.clearSession();
if (this.response && !this.response.headersSent) {
this.response.clearCookie('TILMELDAUTH', {
domain: this.config.cookieDomain,
path: this.config.cookiePath,
});
this.response.set('X-TILMELDAUTH', '');
if (this.response) {
if (clearCookie && !this.response.headersSent) {
this.response.clearCookie('TILMELDAUTH', {
domain: this.config.cookieDomain,
path: this.config.cookiePath,
});
this.response.set('X-TILMELDAUTH', '');
}
this.response.locals.user = null;
}
}

Expand Down

0 comments on commit 922e145

Please sign in to comment.