Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitpod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
tasks:
- init: pnpm install
command: pnpm run start
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,35 @@
# oauth-mock-server

A mocked oauth server for development and e2e testing
> :rotating_light: IMPORTANT: This tool should ONLY be used for development and testing setups!

A mocked oauth server for development and e2e testing.

## Config

You can adjust the default configuration by placing a file `oauth-mock-server.json` in your current working directory:

```json
{
"realm": "my-project",
"users": [
{
"id": "1",
"username": "toni",
"email": "toni@test.com",
"name": "Toni Tester"
},
{
"id": "2",
"username": "alice",
"email": "alice@wonderland.org",
"name": "Alice Wonderland"
},
{
"id": "3",
"username": "herbert",
"email": "her@bert.de",
"name": "Herbert"
}
]
}
```
51 changes: 51 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import fs from 'fs';
import { cwd } from 'node:process';
import path from 'path';

export type ConfigUser = {
id: string;
username: string;
email: string;
name: string;
};

export type Config = {
realm: string;
users: ConfigUser[];
};

export function getConfig(): Config {
const defaultConfig = <Config>{
realm: 'my-project',
users: [
{
id: '1',
username: 'toni',
email: 'toni@test.com',
name: 'Toni Tester',
},
{
id: '2',
username: 'alice',
email: 'alice@wonderland.org',
name: 'Alice Wonderland',
},
{
id: '3',
username: 'herbert',
email: 'her@bert.de',
name: 'Herbert',
},
],
};

const configPath = path.join(cwd(), 'oauth-mock-server.json');
if (!fs.existsSync(configPath)) {
return defaultConfig;
}

const rawConfig = fs.readFileSync(configPath);
const config = <Config>{};
Object.assign(config, defaultConfig, JSON.parse(rawConfig.toString()));
return config;
}
46 changes: 13 additions & 33 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,41 +1,20 @@
import fastifyFormBody from '@fastify/formbody';
import { getConfig } from 'config';
import fastify from 'fastify';
import jwt from 'jsonwebtoken';

const config = getConfig();

const server = fastify();
void server.register(fastifyFormBody);

const realm = 'bookyp'; // TODO: add way to load realm name from config

// TODO: add way to load users from config
const users = [
{
id: '1',
username: 'toni',
email: 'toni@test.com',
name: 'Toni Tester',
},
{
id: '2',
username: 'alice',
email: 'alice@wonderland.org',
name: 'Alice Wonderland',
},
{
id: '3',
username: 'herbert',
email: 'her@bert.de',
name: 'Herbert',
},
];

const jwtSecret = 'mySuperDuperSecret';

const randomString = () => (Math.random() + 1).toString(36).substring(7);

let sessions: { code: string; user_id: string; access_token?: string }[] = [];

server.get(`/auth/realms/${realm}/protocol/openid-connect/auth`, async (request, reply) => {
server.get(`/auth/realms/${config.realm}/protocol/openid-connect/auth`, async (request, reply) => {
const query = request.query as { redirect_uri: string; error?: string };

const template = `
Expand Down Expand Up @@ -84,7 +63,7 @@ server.get(`/auth/realms/${realm}/protocol/openid-connect/auth`, async (request,
<div>
<p>Please login with one of the following usernames:</p>
<ul>
${users
${config.users
.map(
(user) =>
`<li>
Expand All @@ -103,12 +82,13 @@ server.get(`/auth/realms/${realm}/protocol/openid-connect/auth`, async (request,

server.all('/do-login', async (request, reply) => {
const query = (request.body || request.query) as { username: string; redirect_uri: string };
const user = users.find((u) => u.username === query.username) || users.find((u) => u.email === query.username);
const user =
config.users.find((u) => u.username === query.username) || config.users.find((u) => u.email === query.username);
const redirect_uri = query.redirect_uri;

if (!user) {
await reply.redirect(
`/auth/realms/${realm}/protocol/openid-connect/auth?error=invalid_credentials&redirect_uri=${redirect_uri}`,
`/auth/realms/${config.realm}/protocol/openid-connect/auth?error=invalid_credentials&redirect_uri=${redirect_uri}`,
);
return;
}
Expand All @@ -119,7 +99,7 @@ server.all('/do-login', async (request, reply) => {
await reply.redirect(`${redirect_uri}?session_state=${sessionState}&code=${code}`);
});

server.post(`/auth/realms/${realm}/protocol/openid-connect/token`, async (request, reply) => {
server.post(`/auth/realms/${config.realm}/protocol/openid-connect/token`, async (request, reply) => {
const body = request.body as {
grant_type: string;
code: string;
Expand All @@ -139,7 +119,7 @@ server.post(`/auth/realms/${realm}/protocol/openid-connect/token`, async (reques
const payload = {
sub: session.user_id, // TODO check if valid (seems to work somehow)
typ: 'Bearer',
aud: realm,
aud: config.realm,
};

const accessToken = jwt.sign(payload, jwtSecret, { expiresIn: '1h' });
Expand All @@ -152,15 +132,15 @@ server.post(`/auth/realms/${realm}/protocol/openid-connect/token`, async (reques
};
});

server.get(`/auth/realms/${realm}/protocol/openid-connect/userinfo`, (request) => {
server.get(`/auth/realms/${config.realm}/protocol/openid-connect/userinfo`, (request) => {
const headers = request.headers as { authorization: string };
const access_token = headers.authorization.replace('Bearer ', '');
const payload = jwt.verify(access_token, jwtSecret) as { sub: string };
const userId = payload.sub;
return users.find((s) => s.id === userId);
return config.users.find((s) => s.id === userId);
});

server.get(`/auth/realms/${realm}/protocol/openid-connect/logout`, async (request, reply) => {
server.get(`/auth/realms/${config.realm}/protocol/openid-connect/logout`, async (request, reply) => {
const query = request.query as { redirect_uri: string };
await reply.redirect(query.redirect_uri);
});
Expand Down