Skip to content

Commit

Permalink
feat: add ability to pass value to jwt claim called app (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
Teddy-Schmitz authored Oct 16, 2023
1 parent be6a8c3 commit c344b1a
Show file tree
Hide file tree
Showing 13 changed files with 67 additions and 27 deletions.
48 changes: 33 additions & 15 deletions documentation/reference/adminapi.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,9 +79,12 @@ Response:
"attributes": {
"name": "Authy Person",
"email": "[email protected]",
"metadata": {
"tenant": "tenantID"
},
"metadata": {
"company": "Auth Co"
},
"app": {
"tenantID": "1234"
},
"active": 1,
"created": "2023-02-02T21:33:53.926Z",
"updated": "2023-02-02T21:33:53.926Z"
Expand All @@ -93,9 +96,12 @@ Response:
"attributes": {
"name": "Authy Person 2",
"email": "[email protected]",
"metadata": {
"tenant": "tenantID"
},
"metadata": {
"company": "Auth Co"
},
"app": {
"tenantID": "5678"
},
"active": 1,
"created": "2023-02-02T21:34:37.712Z",
"updated": "2023-02-02T21:34:37.712Z"
Expand All @@ -118,7 +124,7 @@ Description: Creates a new user in the Authcompanion database.

Bearer Token Required: `Authorization: Bearer {admin access token}`

Pass an arbitrary object to data.attributes.metdata which will be made available as a claim on the user's JWT issued after login.
Optional: Pass an arbitrary object to data.attributes.metadata which will be made available as a claim on the user's JWT issued after login, this claim is changable using the user token. Pass an arbitrary object to data.attributes.app which will be made available as a claim on the user's JWT issued after login, this claim is changable only using the admin token (aka a "private" claim).

**POST** Request Body:

Expand All @@ -131,9 +137,12 @@ Pass an arbitrary object to data.attributes.metdata which will be made available
"email": "[email protected]",
"password": "supersecret",
"metadata": {
"tenant": "tenantID"
"company": "Auth Co"
},
"active": 1,
"app": {
"tenantID": "1234"
},
"active": 1
}
}
}
Expand All @@ -150,7 +159,10 @@ Response:
"name": "Authy Person",
"email": "[email protected]",
"metadata": {
"tenant": "tenantID"
"company": "Auth Co"
},
"app": {
"tenantID": "1234"
},
"active": 1,
"created": "2023-02-02T21:33:53.926Z",
Expand All @@ -168,7 +180,7 @@ Description: Updates a single user from the Authcompanion database with the user

Bearer Token Required: `Authorization: Bearer {admin access token}`

Pass an arbitrary object to data.attributes.metdata which will be made available as a claim on the user's JWT issued after login.
Optional: Pass an arbitrary object to data.attributes.metadata which will be made available as a claim on the user's JWT issued after login, this claim is changable using the user token. Pass an arbitrary object to data.attributes.app which will be made available as a claim on the user's JWT issued after login, this claim is changable only using the admin token (aka a "private" claim).

**PATCH** Request Body:

Expand All @@ -182,8 +194,11 @@ Pass an arbitrary object to data.attributes.metdata which will be made available
"password": "supersecret",
"active": 1,
"metadata": {
"tenant": "tenantID",
},
"tenant": "tenantID"
},
"app": {
"tenantID": "1234"
}
}
}
}
Expand All @@ -200,8 +215,11 @@ Response:
"name": "Authy Person",
"email": "[email protected]",
"metadata": {
"tenant": "tenantID",
},
"tenant": "tenantID"
},
"app": {
"tenantID": "1234"
},
"active": 1,
"created": "2023-02-02T21:33:53.926Z",
"updated": "2023-02-02T21:33:53.926Z"
Expand Down
10 changes: 5 additions & 5 deletions documentation/reference/authapi.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ Returns Content-Type: application/json

Description: Register a user. Returns a JWT access token and sets a refresh token (as a http only cookie). JWTs are used by your web application to authenticate a user with your backend APIs.

Optional: Pass an arbitrary object to data.attributes.metdata which will be made available as a claim on the user's JWT issued after login.
Optional: Pass an arbitrary object to data.attributes.metdata which will be made available as a claim on the user's JWT issued after login (aka a "public" claim).

**POST** Request Body:

Expand All @@ -29,8 +29,8 @@ Optional: Pass an arbitrary object to data.attributes.metdata which will be made
"name": "Authy Person",
"email": "[email protected]",
"password": "mysecretpass",
"metadata": {
"tenant": "tenantID"
"metadata": {
"company": "Auth Co"
}
}
}
Expand Down Expand Up @@ -105,7 +105,7 @@ Bearer Token Required: `Authorization: Bearer {user's access token}`

All fields in the user's attributes are optional.

Pass an arbitrary object to data.attributes.metdata which will be made available as a claim on the user's JWT issued after login.
Optional: Pass an arbitrary object to data.attributes.metdata which will be made available as a claim on the user's JWT issued after login.

**POST** Request Body:

Expand All @@ -118,7 +118,7 @@ Pass an arbitrary object to data.attributes.metdata which will be made available
"email": "[email protected]",
"password": "mysecretpass",
"metadata": {
"tenant": "tenantID"
"company": "Auth Co"
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion plugins/db/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Database from "better-sqlite3";
import config from "../../config.js";
import fastifyPlugin from "fastify-plugin";

const VERSION = 3;
const VERSION = 4;

const migrate = (db, version) => {
const allFiles = readdirSync("./plugins/db/schema/");
Expand Down
7 changes: 7 additions & 0 deletions plugins/db/schema/4__appdata.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
BEGIN TRANSACTION;

ALTER TABLE users ADD COLUMN appdata text;

UPDATE authc_version SET version = 4 WHERE version = 3;

COMMIT TRANSACTION;
4 changes: 3 additions & 1 deletion services/admin/users/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,15 @@ export const createUserHandler = async function (request, reply) {
const jwtid = randomUUID();

const registerStmt = this.db.prepare(
"INSERT INTO users (uuid, name, email, password, metadata, active, jwt_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%fZ','now'), strftime('%Y-%m-%dT%H:%M:%fZ','now')) RETURNING uuid, name, email, metadata, active, created_at, updated_at;"
"INSERT INTO users (uuid, name, email, password, metadata, appdata, active, jwt_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%fZ','now'), strftime('%Y-%m-%dT%H:%M:%fZ','now')) RETURNING uuid, name, email, metadata, appdata, active, created_at, updated_at;"
);
const user = registerStmt.get(
uuid,
request.body.data.attributes.name,
request.body.data.attributes.email,
hashpwd,
JSON.stringify(request.body.data.attributes.metadata),
JSON.stringify(request.body.data.attributes.app),
request.body.data.attributes.active,
jwtid
);
Expand All @@ -59,6 +60,7 @@ export const createUserHandler = async function (request, reply) {
name: user.name,
email: user.email,
metadata: JSON.parse(user.metadata),
app: JSON.parse(user.appdata),
active: user.active,
created: user.created_at,
updated: user.updated_at,
Expand Down
3 changes: 2 additions & 1 deletion services/admin/users/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const listUsersHandler = async function (request, reply) {
const offset = (page - 1) * size;

// Prepare the SQL query and parameters
let query = "SELECT uuid, name, email, metadata, active, created_at, updated_at FROM users";
let query = "SELECT uuid, name, email, metadata, appdata, active, created_at, updated_at FROM users";
let params = [];

if (searchEmail) {
Expand All @@ -35,6 +35,7 @@ export const listUsersHandler = async function (request, reply) {
name: user.name,
email: user.email,
metadata: JSON.parse(user.metadata),
app: JSON.parse(user.appdata),
active: user.active,
created: user.created_at,
updated: user.updated_at,
Expand Down
1 change: 1 addition & 0 deletions services/admin/users/schema/createSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const createSchema = {
email: { type: "string" },
password: { type: "string" },
metadata: { type: "object" },
app: { type: "object" },
active: { type: "string" },
},
required: ["name", "email", "password", "active"],
Expand Down
1 change: 1 addition & 0 deletions services/admin/users/schema/updateSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const updateSchema = {
email: { type: "string" },
password: { type: "string" },
metadata: { type: "object" },
app: { type: "object" },
active: { type: "string" },
},
},
Expand Down
4 changes: 3 additions & 1 deletion services/admin/users/update.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,14 @@ export const updateUserHandler = async function (request, reply) {

//Per json-api spec: If a request does not include all of the attributes for a resource, the server MUST interpret the missing attributes as if they were included with their current values. The server MUST NOT interpret missing attributes as null values.
const updateStmt = this.db.prepare(
"UPDATE users SET name = coalesce(?, name), email = coalesce(?, email), password = coalesce(?, password), metadata = coalesce(?, metadata), active = coalesce(?, active), updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now') WHERE uuid = ? RETURNING uuid, name, email, metadata, active, created_at, updated_at;"
"UPDATE users SET name = coalesce(?, name), email = coalesce(?, email), password = coalesce(?, password), metadata = coalesce(?, metadata), appdata = coalesce(?, appdata), active = coalesce(?, active), updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now') WHERE uuid = ? RETURNING uuid, name, email, metadata, appdata, active, created_at, updated_at;"
);
const updatedUser = updateStmt.get(
request.body.data.attributes.name,
request.body.data.attributes.email,
request.body.data.attributes.password,
JSON.stringify(request.body.data.attributes.metadata),
JSON.stringify(request.body.data.attributes.app),
request.body.data.attributes.active,
request.params.uuid
);
Expand All @@ -70,6 +71,7 @@ export const updateUserHandler = async function (request, reply) {
name: updatedUser.name,
email: updatedUser.email,
metadata: JSON.parse(user.metadata),
app: JSON.parse(user.appdata),
active: updatedUser.active,
created: updatedUser.created_at,
updated: updatedUser.updated_at,
Expand Down
2 changes: 1 addition & 1 deletion services/auth/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export const loginHandler = async function (request, reply) {

// Fetch user from database
const stmt = this.db.prepare(
"SELECT uuid, name, email, jwt_id, password, active, created_at, updated_at, metadata FROM users WHERE email = ?;"
"SELECT uuid, name, email, jwt_id, password, active, created_at, updated_at, metadata, appdata FROM users WHERE email = ?;"
);
const userObj = await stmt.get(request.body.data.attributes.email);

Expand Down
2 changes: 1 addition & 1 deletion services/auth/profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const userProfileHandler = async function (request, reply) {

//Per json-api spec: If a request does not include all of the attributes for a resource, the server MUST interpret the missing attributes as if they were included with their current values. The server MUST NOT interpret missing attributes as null values.
const updateStmt = this.db.prepare(
"UPDATE users SET name = coalesce(?, name), email = coalesce(?, email), password = coalesce(?, password), metadata = coalesce(?, metadata), updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now') WHERE uuid = ? RETURNING uuid, name, email, metadata, jwt_id, active, created_at, updated_at;"
"UPDATE users SET name = coalesce(?, name), email = coalesce(?, email), password = coalesce(?, password), metadata = coalesce(?, metadata), updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now') WHERE uuid = ? RETURNING uuid, name, email, metadata, appdata, jwt_id, active, created_at, updated_at;"
);
const userObj = updateStmt.get(
request.body.data.attributes.name,
Expand Down
2 changes: 1 addition & 1 deletion services/auth/registration.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const registrationHandler = async function (request, reply) {
const jwtid = randomUUID();

const registerStmt = this.db.prepare(
"INSERT INTO users (uuid, name, email, password, metadata, active, jwt_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%fZ','now'), strftime('%Y-%m-%dT%H:%M:%fZ','now')) RETURNING uuid, name, email, metadata, jwt_id, created_at, updated_at;"
"INSERT INTO users (uuid, name, email, password, metadata, active, jwt_id, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, strftime('%Y-%m-%dT%H:%M:%fZ','now'), strftime('%Y-%m-%dT%H:%M:%fZ','now')) RETURNING uuid, name, email, metadata, appdata, jwt_id, created_at, updated_at;"
);
const userObj = registerStmt.get(
uuid,
Expand Down
8 changes: 8 additions & 0 deletions utils/jwt.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ export async function makeAccesstoken(userObj, secretKey) {
claims.metadata = JSON.parse(userObj.metadata);
}

if (userObj.appdata !== undefined && userObj.appdata !== null && userObj.appdata !== "{}") {
claims.app = JSON.parse(userObj.appdata);
}

const jwt = await new jose.SignJWT(claims)
.setProtectedHeader({ alg: "HS256", typ: "JWT" })
.setIssuedAt()
Expand Down Expand Up @@ -74,6 +78,10 @@ export async function makeRefreshtoken(userObj, secretKey, { recoveryToken = fal
claims.metadata = JSON.parse(userObj.metadata);
}

if (userObj.appdata !== undefined && userObj.appdata !== null && userObj.appdata !== "{}") {
claims.app = JSON.parse(userObj.appdata);
}

const db = new Database(config.DBPATH);

const jwtid = randomUUID();
Expand Down

0 comments on commit c344b1a

Please sign in to comment.