Skip to content

Commit

Permalink
feat: add ability to pass value to a new metadata claim (#15)
Browse files Browse the repository at this point in the history
  • Loading branch information
Teddy-Schmitz authored Oct 12, 2023
1 parent 9e45481 commit e1dca1f
Show file tree
Hide file tree
Showing 15 changed files with 103 additions and 133 deletions.
6 changes: 2 additions & 4 deletions 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 = 2;
const VERSION = 3;

const migrate = (db, version) => {
const allFiles = readdirSync("./plugins/db/schema/");
Expand Down Expand Up @@ -57,9 +57,7 @@ const dbPlugin = async function (fastify) {
fastify.log.info(`Using Sqlite3 Database: ${config.DBPATH}`);
} catch (error) {
console.log(error);
throw new Error(
"There was an error setting and connecting up the Database, please try again!"
);
throw new Error("There was an error setting and connecting up the Database, please try again!");
}
//make available the database across the server by calling "db"
fastify.decorate("db", db);
Expand Down
7 changes: 7 additions & 0 deletions plugins/db/schema/3__metadata.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
BEGIN TRANSACTION;

ALTER TABLE users ADD COLUMN metadata text;

UPDATE authc_version SET version = 3 WHERE version = 2;

COMMIT TRANSACTION;
27 changes: 8 additions & 19 deletions services/admin/users/create.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ export const createUserHandler = async function (request, reply) {
try {
//Check the request's type attibute is set to users
if (request.body.data.type !== "users") {
request.log.info(
"Admin API: The request's type is not set to Users, creation failed"
);
request.log.info("Admin API: The request's type is not set to Users, creation failed");
throw { statusCode: 400, message: "Invalid Type Attribute" };
}

Expand All @@ -17,34 +15,24 @@ export const createUserHandler = async function (request, reply) {
const duplicateAccount = await stmt.get(request.body.data.attributes.email);

if (duplicateAccount) {
request.log.info(
"Admin API: User's email already exists in database, creation failed"
);
request.log.info("Admin API: User's email already exists in database, creation failed");
throw { statusCode: 400, message: "Duplicate Email Address Exists" };
}

//If the user's active status is a string, convert it to a number
if (request.body.data.attributes.active) {
if (typeof request.body.data.attributes.active === "string") {
request.body.data.attributes.active = Number(
request.body.data.attributes.active
);
request.body.data.attributes.active = Number(request.body.data.attributes.active);
}
}

//Check if the user's active status is being updated and if it is, check if the new status is a valid 1 or 0
if (request.body.data.attributes.active) {
if (
request.body.data.attributes.active !== 0 &&
request.body.data.attributes.active !== 1
) {
request.log.info(
"Admin API: User's active status is not valid, update failed"
);
if (request.body.data.attributes.active !== 0 && request.body.data.attributes.active !== 1) {
request.log.info("Admin API: User's active status is not valid, update failed");
throw {
statusCode: 400,
message:
"Invalid Active Status, Please use 1 for true and 0 for false",
message: "Invalid Active Status, Please use 1 for true and 0 for false",
};
}
}
Expand All @@ -55,13 +43,14 @@ export const createUserHandler = async function (request, reply) {
const jwtid = randomUUID();

const registerStmt = this.db.prepare(
"INSERT INTO users (uuid, name, email, password, 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, active, 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, 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),
request.body.data.attributes.active,
jwtid
);
Expand Down
30 changes: 8 additions & 22 deletions services/admin/users/list.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
export const listUsersHandler = async function (request, reply) {
try {
const {
"page[number]": pageNumber = 1,
"page[size]": pageSize = 10,
"search[email]": searchEmail,
} = request.query;
const { "page[number]": pageNumber = 1, "page[size]": pageSize = 10, "search[email]": searchEmail } = request.query;

// Convert the page number and size to integers
const page = parseInt(pageNumber);
Expand All @@ -14,8 +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, active, created_at, updated_at FROM users";
let query = "SELECT uuid, name, email, metadata, active, created_at, updated_at FROM users";
let params = [];

if (searchEmail) {
Expand All @@ -39,6 +34,7 @@ export const listUsersHandler = async function (request, reply) {
attributes: {
name: user.name,
email: user.email,
metadata: JSON.parse(user.metadata),
active: user.active,
created: user.created_at,
updated: user.updated_at,
Expand All @@ -53,9 +49,7 @@ export const listUsersHandler = async function (request, reply) {
}

const countStmt = this.db.prepare(countQuery);
const totalCount = await countStmt.get(
...(searchEmail ? [`%${searchEmail}%`] : [])
); // Spread the searchEmail param if provided
const totalCount = await countStmt.get(...(searchEmail ? [`%${searchEmail}%`] : [])); // Spread the searchEmail param if provided

// Calculate pagination metadata
const totalPages = Math.ceil(totalCount.count / size);
Expand All @@ -66,24 +60,16 @@ export const listUsersHandler = async function (request, reply) {
const paginationLinks = {};

if (hasNextPage) {
paginationLinks.next = `${request.raw.url.split("?")[0]}?page[number]=${
page + 1
}&page[size]=${size}`;
paginationLinks.next = `${request.raw.url.split("?")[0]}?page[number]=${page + 1}&page[size]=${size}`;
}

if (hasPreviousPage) {
paginationLinks.prev = `${request.raw.url.split("?")[0]}?page[number]=${
page - 1
}&page[size]=${size}`;
paginationLinks.prev = `${request.raw.url.split("?")[0]}?page[number]=${page - 1}&page[size]=${size}`;
}

if (totalPages > 0) {
paginationLinks.first = `${
request.raw.url.split("?")[0]
}?page[number]=1&page[size]=${size}`;
paginationLinks.last = `${
request.raw.url.split("?")[0]
}?page[number]=${totalPages}&page[size]=${size}`;
paginationLinks.first = `${request.raw.url.split("?")[0]}?page[number]=1&page[size]=${size}`;
paginationLinks.last = `${request.raw.url.split("?")[0]}?page[number]=${totalPages}&page[size]=${size}`;
}

// Send the server reply
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" },
active: { type: "string" },
metadata: { type: "object" },
},
required: ["name", "email", "password", "active"],
},
Expand Down
16 changes: 8 additions & 8 deletions services/admin/users/schema/loginSchema.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
export const loginSchema = {
schema: {
body: {
type: 'object',
type: "object",
properties: {
data: {
type: 'object',
type: "object",
properties: {
attributes: {
type: 'object',
type: "object",
properties: {
email: { type: 'string' },
password: { type: 'string' },
email: { type: "string" },
password: { type: "string" },
},
required: ['email', 'password'],
required: ["email", "password"],
},
},
required: ['type', 'attributes'],
required: ["type", "attributes"],
},
},
required: ['data'],
required: ["data"],
},
},
};
1 change: 1 addition & 0 deletions services/admin/users/schema/updateSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const updateSchema = {
name: { type: "string" },
email: { type: "string" },
password: { type: "string" },
metadata: { type: "object" },
},
},
},
Expand Down
36 changes: 10 additions & 26 deletions services/admin/users/update.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ export const updateUserHandler = async function (request, reply) {
try {
//Check the request's type attibute is set to users
if (request.body.data.type !== "users") {
request.log.info(
"Admin API: The request's type is not set to Users, update failed"
);
request.log.info("Admin API: The request's type is not set to Users, update failed");
throw { statusCode: 400, message: "Invalid Type Attribute" };
}

Expand All @@ -15,24 +13,17 @@ export const updateUserHandler = async function (request, reply) {
const user = await userStmt.get(request.params.uuid);

if (!user) {
request.log.info(
"Admin API: User does not exist in database, update failed"
);
request.log.info("Admin API: User does not exist in database, update failed");
throw { statusCode: 404, message: "User Not Found" };
}

//Check if the user's email is being updated and if its not the same email as the user's current email, check if the new email is already in use
if (
request.body.data.attributes.email &&
request.body.data.attributes.email !== user.email
) {
if (request.body.data.attributes.email && request.body.data.attributes.email !== user.email) {
const emailStmt = this.db.prepare("SELECT * FROM users WHERE email = ?;");
const email = await emailStmt.get(request.body.data.attributes.email);

if (email) {
request.log.info(
"Admin API: User's email is already in use, update failed"
);
request.log.info("Admin API: User's email is already in use, update failed");
throw { statusCode: 400, message: "Email Already In Use" };
}
}
Expand All @@ -46,37 +37,30 @@ export const updateUserHandler = async function (request, reply) {
//If the user's active status is a string, convert it to a number
if (request.body.data.attributes.active) {
if (typeof request.body.data.attributes.active === "string") {
request.body.data.attributes.active = Number(
request.body.data.attributes.active
);
request.body.data.attributes.active = Number(request.body.data.attributes.active);
}
}

//Check if the user's active status is being updated and if it is, check if the new status is a valid 1 or 0
if (request.body.data.attributes.active) {
if (
request.body.data.attributes.active !== 0 &&
request.body.data.attributes.active !== 1
) {
request.log.info(
"Admin API: User's active status is not valid, update failed"
);
if (request.body.data.attributes.active !== 0 && request.body.data.attributes.active !== 1) {
request.log.info("Admin API: User's active status is not valid, update failed");
throw {
statusCode: 400,
message:
"Invalid Active Status, Please use 1 for true and 0 for false",
message: "Invalid Active Status, Please use 1 for true and 0 for false",
};
}
}

//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), active = coalesce(?, active), updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now') WHERE uuid = ? RETURNING uuid, name, email, active, created_at, updated_at;"
"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;"
);
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),
request.body.data.attributes.active,
request.params.uuid
);
Expand Down
15 changes: 4 additions & 11 deletions services/auth/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,19 @@ export const loginHandler = async function (request, reply) {
try {
// Check the request's type attibute is set to users
if (request.body.data.type !== "users") {
request.log.info(
"Auth API: The request's type is not set to Users, creation failed"
);
request.log.info("Auth API: The request's type is not set to Users, creation failed");
throw { statusCode: 400, message: "Invalid Type Attribute" };
}

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

// Check if user does not exist in the database
if (!userObj) {
request.log.info(
"Auth API: User does not exist in database, login failed"
);
request.log.info("Auth API: User does not exist in database, login failed");
throw { statusCode: 400, message: "Login Failed" };
}

Expand All @@ -32,10 +28,7 @@ export const loginHandler = async function (request, reply) {
throw { statusCode: 400, message: "Login Failed" };
}

const passwordCheckResult = await verifyValueWithHash(
request.body.data.attributes.password,
userObj.password
);
const passwordCheckResult = await verifyValueWithHash(request.body.data.attributes.password, userObj.password);

// Check if user has the correct password
if (!passwordCheckResult) {
Expand Down
11 changes: 4 additions & 7 deletions services/auth/profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@ export const userProfileHandler = async function (request, reply) {
try {
//Check the request's type attibute is set to users
if (request.body.data.type !== "users") {
request.log.info(
"Auth API: The request's type is not set to Users, update failed"
);
request.log.info("Auth API: The request's type is not set to Users, update failed");
throw { statusCode: 400, message: "Invalid Type Attribute" };
}

Expand All @@ -18,9 +16,7 @@ export const userProfileHandler = async function (request, reply) {

//Check if the user exists already
if (!user) {
request.log.info(
"Auth API: User does not exist in database, update failed"
);
request.log.info("Auth API: User does not exist in database, update failed");
throw { statusCode: 400, message: "Profile Update Failed" };
}

Expand All @@ -32,12 +28,13 @@ 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), updated_at = strftime('%Y-%m-%dT%H:%M:%fZ','now') WHERE uuid = ? RETURNING uuid, name, email, 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, jwt_id, active, created_at, updated_at;"
);
const userObj = updateStmt.get(
request.body.data.attributes.name,
request.body.data.attributes.email,
request.body.data.attributes.password,
JSON.stringify(request.body.data.attributes.metadata),
request.jwtRequestPayload.userid
);

Expand Down
Loading

0 comments on commit e1dca1f

Please sign in to comment.