Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
87 commits
Select commit Hold shift + click to select a range
e8e657e
Validation: Strip unkown + Express/Auth added security, updated resou…
ben-shepherd Sep 23, 2024
c26d3c1
Merge pull request #34 from ben-shepherd/express-auth-security-feature
ben-shepherd Sep 23, 2024
bc6e7c8
Added Security to express routes (progress, pre-refactor)
ben-shepherd Sep 24, 2024
7e522af
Refactored files into Express Domain
ben-shepherd Sep 24, 2024
6010f28
Fixed imports, eslint
ben-shepherd Sep 24, 2024
2e66155
Refactored security interfaces into single file
ben-shepherd Sep 24, 2024
923f08a
Removed console logs
ben-shepherd Sep 24, 2024
9e7a116
Updated comment
ben-shepherd Sep 24, 2024
7a67ec1
Merge pull request #35 from ben-shepherd/express-routing-security
ben-shepherd Sep 24, 2024
ee86906
CurrentRequest to only store userId and not entire User instance
ben-shepherd Sep 24, 2024
6e6eaef
Added scopes to authentication domain, updated authorizeMiddleware to…
ben-shepherd Sep 24, 2024
fff361b
Merge pull request #36 from ben-shepherd/auth-scopes
ben-shepherd Sep 24, 2024
984f76f
Refactored Security Rules, added hasScope Security rule
ben-shepherd Sep 25, 2024
9d1135e
Fixed incorrect import path
ben-shepherd Sep 25, 2024
9341f74
Updated comments
ben-shepherd Sep 25, 2024
dcfb358
Fix arguments not working as expected in ListRoutesCommand
ben-shepherd Sep 25, 2024
f8977af
Added Resource scopes, added scope security enabled option
ben-shepherd Sep 25, 2024
9ae448c
Added todo comment
ben-shepherd Sep 25, 2024
f6af76c
Merge pull request #37 from ben-shepherd/auth-scopes
ben-shepherd Sep 25, 2024
30a5074
Fixed incorrect security identifier
ben-shepherd Sep 25, 2024
d311066
Fixed incorrect custom identifier, use user provided identifier
ben-shepherd Sep 25, 2024
c721dbe
Updated CurrentRequest to handle ipAddresses
ben-shepherd Sep 25, 2024
fac5467
Added RateLimit security
ben-shepherd Sep 25, 2024
47a4559
Refactored CurrentRequest into an app container
ben-shepherd Sep 25, 2024
5f36ea1
CurrentRequest to become RequestContext, tested rate limiting securit…
ben-shepherd Sep 26, 2024
de42ecf
Updated securityMiddleware comments
ben-shepherd Sep 26, 2024
84a8cce
Updated Observer with awaitable methods
ben-shepherd Sep 26, 2024
0b5f650
Auth permisisons (progress)
ben-shepherd Sep 26, 2024
4efa054
Missed files
ben-shepherd Sep 26, 2024
442ac8a
fix(migrationss): Fix migration failing when file missing
ben-shepherd Sep 26, 2024
bd00a08
Merge branch 'develop' into auth-permissions
ben-shepherd Sep 26, 2024
7789d43
Added permission groups, user groups and roles, ApiToken scopes, Rout…
ben-shepherd Sep 27, 2024
5dd4154
Updated comment
ben-shepherd Sep 27, 2024
e3c18f1
Merge pull request #39 from ben-shepherd/auth-permissions
ben-shepherd Sep 27, 2024
8eaaddc
Added alsoArguments option, perform enableScopes check in ExpressServ…
ben-shepherd Sep 27, 2024
86bf9d5
Updated RouteResource to allow for partial scopes (fixes scope all)
ben-shepherd Sep 27, 2024
c4b8df6
Remove hasScope from Security (prefer define scopes on route instead)
ben-shepherd Sep 27, 2024
1a7adc2
Revert IIdentifiableSecurityCallback to string only also, removed arg…
ben-shepherd Sep 27, 2024
2df6075
Updated comment
ben-shepherd Sep 27, 2024
677f2ea
Updated comment
ben-shepherd Sep 27, 2024
da17230
Merge pull request #40 from ben-shepherd/auth-permissions
ben-shepherd Sep 27, 2024
b44929c
Fix potential headers already sent bug
ben-shepherd Sep 27, 2024
c7737a1
Fixed missing properties on auth test
ben-shepherd Sep 28, 2024
ca9e493
Merge pull request #41 from ben-shepherd/auth-permissions
ben-shepherd Sep 28, 2024
d69a582
feat(commands): Commands can be registered with configs in the same m…
ben-shepherd Sep 28, 2024
e80d4fd
feat(auth): Configurable when tokens expire
ben-shepherd Sep 28, 2024
8fe11ba
refactor(express): Renamed name to path in IRouteResourceOptions
ben-shepherd Sep 28, 2024
51ea3a2
feat(express): Updated logRoute with security rules
ben-shepherd Sep 29, 2024
f4e990e
feat(express): Added index, all filters to RouteResources
ben-shepherd Sep 29, 2024
f8db6a5
Merge pull request #42 from ben-shepherd/blog-tutorial-example
ben-shepherd Sep 29, 2024
06e230a
refactor(make): Updated migration template
ben-shepherd Sep 29, 2024
381072f
fix(models): Remove Model when generating default table name
ben-shepherd Sep 29, 2024
b94862c
refactor(make): Updated migration template
ben-shepherd Sep 29, 2024
bb5cd6c
refactor(make): Updated migration table
ben-shepherd Sep 29, 2024
e71ae0f
feat(database): Added limit, skip functionality
ben-shepherd Sep 29, 2024
9995bf4
feat(express): Added pagination to route resource
ben-shepherd Sep 29, 2024
535ff55
refactor(test): Fix import on auth.test.ts
ben-shepherd Sep 29, 2024
29f4ff3
docs(changelist): Added changelist.md and bumped version to 1.1.0
ben-shepherd Oct 2, 2024
728e2e5
feat(express): Added pageSizeAllowOverride option
ben-shepherd Oct 3, 2024
f5e5df9
refactor(kernel): set app environment before loading providers
ben-shepherd Oct 5, 2024
5cd0802
refactor(express): Refactored resource index into service
ben-shepherd Oct 6, 2024
0f51ba4
feat(express): Added ResourceErrorService
ben-shepherd Oct 6, 2024
d907b95
refactor(express): Refactored resource show into a service
ben-shepherd Oct 6, 2024
7f78407
refactor(express): Refactored resource create into a service
ben-shepherd Oct 6, 2024
e0c62b7
refactor(express): Refactored resource delete into a service
ben-shepherd Oct 6, 2024
d2fa72f
refactor(express): Refactored resource update into its own service, a…
ben-shepherd Oct 6, 2024
cc7c4dd
Removed unused import
ben-shepherd Oct 6, 2024
e6899c5
Merge pull request #43 from ben-shepherd/blog-tutorial-example
ben-shepherd Oct 6, 2024
3a13641
feature(database): Added partial searching to Postgres Document Manager
ben-shepherd Oct 6, 2024
18a10d2
feature(database): Added partial searching to MongoDB DocumentManager
ben-shepherd Oct 6, 2024
5e8f856
test(database): Added db partial searching test
ben-shepherd Oct 6, 2024
9224713
feature(express): Added ability to apply request query filters for re…
ben-shepherd Oct 7, 2024
5eca7f1
fix(database): Fixed unhandled non string type in Postgres buiilder
ben-shepherd Oct 7, 2024
9fe635d
refactor/fix(migrations): Allow configurable migration model, uses di…
ben-shepherd Oct 7, 2024
5a5fce9
fix(migrations): Missing model constructor parameter
ben-shepherd Oct 7, 2024
b3e2197
feat(logger): Added loggerService using winston logger
ben-shepherd Oct 12, 2024
cc42b77
refactor(logger): Updated console methods to use new logger service
ben-shepherd Oct 12, 2024
9ed0b63
Merge pull request #44 from ben-shepherd/logger
ben-shepherd Oct 12, 2024
c74f8af
feat(model): added attr method shorthand for setAttribute and getAttr…
ben-shepherd Oct 12, 2024
601ad96
refator(tests): Added missing logger provider to tests
ben-shepherd Oct 12, 2024
60f0750
feat(logger): Updated formatting of logger
ben-shepherd Oct 12, 2024
35f732f
refactor(database,sequelize): Turned off SQL logging for production e…
ben-shepherd Oct 12, 2024
d12cdad
feat(model): Added dirty methods to models, updated data property to …
ben-shepherd Oct 12, 2024
29c88c4
documentation(readme): Added write permissions to quick setup
ben-shepherd Oct 13, 2024
8e8d560
fix(stripGuardedResourceProperties): fix ts complain
ben-shepherd Oct 13, 2024
4dbf222
fix(resources): fixed missing delete resource logic
ben-shepherd Oct 13, 2024
fa4def9
updated banner
ben-shepherd Oct 14, 2024
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
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ APP_EVENT_DRIVER=sync
APP_WORKER_DRIVER=queue

JWT_SECRET=
JWT_EXPIRES_IN_MINUTES=60

DATABASE_DEFAULT_CONNECTION=default
DATABASE_DEFAULT_PROVIDER=
Expand Down
Binary file removed assets/banner_black.png
Binary file not shown.
Binary file added assets/banner_blank.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 43 additions & 0 deletions changelist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
## Version 1.0.1 (Beta)

### Security Enhancements
- Added security features to Express routes
- Implemented rate limiting
- Added configurable token expiration
- Introduced user scopes, resource scopes, and API token scopes
- Implemented permission groups, user groups, and roles

### Authentication and Authorization
- Refactored security rules and middleware
- Updated authorization middleware to include scopes
- Improved handling of custom identifiers

### Request Handling
- Refactored CurrentRequest into RequestContext
- Added IP address handling to RequestContext
- Moved RequestContext into an app container

### Route Resources
- Added 'index' and 'all' filters to RouteResources
- Renamed 'name' to 'path' in IRouteResourceOptions
- Updated to allow for partial scopes

### Command Handling
- Fixed argument processing in ListRoutesCommand
- Enabled registration of commands with configs in the same module

### Code Refactoring and Optimization
- Consolidated security interfaces into a single file
- Removed debug console logs
- Fixed incorrect import paths
- Refactored Express domain files

### Bug Fixes
- Resolved a potential "headers already sent" issue
- Fixed migration failures related to missing files
- Corrected custom identifier handling

### Miscellaneous
- Updated Observer with awaitable methods
- Improved route logging to include security rules
- Various comment updates for improved clarity
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "larascript-framework",
"version": "1.0.1",
"version": "1.1.1",
"description": "A Node.js framework inspired by Laravel made with TypeScript",
"main": "index.js",
"scripts": {
Expand Down Expand Up @@ -35,6 +35,7 @@
"sequelize": "^6.37.3",
"sqlite3": "^5.1.7",
"uuid": "^10.0.0",
"winston": "^3.15.0",
"zod": "^3.23.8"
},
"devDependencies": {
Expand Down
18 changes: 14 additions & 4 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,17 @@ Follow these steps to quickly set up your project:

This will install all the necessary dependencies for your project.

3. **Start Database Containers**:
3. **Add write permissions to logs directory**

After installing dependencies, you need to add write permissions to the logs directory:

```
chmod -R 755 /path/to/larascript/storage/logs
```

This ensures that your application can write log files as needed.

4. **Start Database Containers**:

To set up your database environment, run:

Expand All @@ -82,7 +92,7 @@ Follow these steps to quickly set up your project:

This command will start the necessary database containers for your project.

4. **Run the setup command (optional)**:
5. **Run the setup command (optional)**:

If you want to populate the .env file with configured settings, use:

Expand All @@ -92,7 +102,7 @@ Follow these steps to quickly set up your project:

This step is optional but can be helpful for quickly configuring your environment.

5. **Run database migrations**:
6. **Run database migrations**:

To set up your database schema, run:

Expand All @@ -102,7 +112,7 @@ Follow these steps to quickly set up your project:

This command will apply all pending database migrations.

6. **Start developing**:
7. **Start developing**:

To start your development server, use:

Expand Down
7 changes: 4 additions & 3 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import appConfig from '@src/config/app';
import CommandNotFoundException from '@src/core/domains/console/exceptions/CommandNotFoundException';
import CommandBootService from '@src/core/domains/console/service/CommandBootService';
import Kernel, { KernelOptions } from '@src/core/Kernel';
import { App } from '@src/core/services/App';

(async () => {
try {
Expand All @@ -16,22 +17,22 @@ import Kernel, { KernelOptions } from '@src/core/Kernel';
*/
await Kernel.boot(appConfig, options);

console.log('[App]: Started');
App.container('logger').info('[App]: Started');

/**
* Execute commands
*/
await cmdBoot.boot(args);

}
}
catch (err) {

// We can safetly ignore CommandNotFoundExceptions
if(err instanceof CommandNotFoundException) {
return;
}

console.error('[App]: Failed to start', err);
App.container('logger').error('[App]: Failed to start', err);
throw err;
}
})();
2 changes: 1 addition & 1 deletion src/app/commands/ExampleCommand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export default class ExampleCommand extends BaseCommand {
signature: string = 'app:example';

async execute() {
console.log('Hello world!')
// Handle the logic
}

}
5 changes: 3 additions & 2 deletions src/app/events/listeners/ExampleListener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import EventListener from "@src/core/domains/events/services/EventListener";

export class ExampleListener extends EventListener<{userId: string}> {

// eslint-disable-next-line no-unused-vars
handle = async (payload: { userId: string}) => {
console.log('[ExampleListener]', payload.userId)
// Handle the logic
}

}
3 changes: 2 additions & 1 deletion src/app/migrations/2024-09-06-create-api-token-table.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import ApiToken from "@src/app/models/auth/ApiToken";
import BaseMigration from "@src/core/domains/migrations/base/BaseMigration";
import { DataTypes } from "sequelize";
import ApiToken from "@src/app/models/auth/ApiToken";

export class CreateApiTokenMigration extends BaseMigration {

Expand All @@ -17,6 +17,7 @@ export class CreateApiTokenMigration extends BaseMigration {
await this.schema.createTable(this.table, {
userId: DataTypes.STRING,
token: DataTypes.STRING,
scopes: DataTypes.JSON,
revokedAt: DataTypes.DATE
})
}
Expand Down
3 changes: 2 additions & 1 deletion src/app/migrations/2024-09-06-create-user-table.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import User from "@src/app/models/auth/User";
import BaseMigration from "@src/core/domains/migrations/base/BaseMigration";
import { DataTypes } from "sequelize";
import User from "@src/app/models/auth/User";

export class CreateUserModelMigration extends BaseMigration {

Expand All @@ -22,6 +22,7 @@ export class CreateUserModelMigration extends BaseMigration {
await this.schema.createTable(this.table, {
email: DataTypes.STRING,
hashedPassword: DataTypes.STRING,
groups: DataTypes.JSON,
roles: DataTypes.JSON,
firstName: stringNullable,
lastName: stringNullable,
Expand Down
34 changes: 34 additions & 0 deletions src/app/models/auth/ApiToken.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import User from '@src/app/models/auth/User';
import ApiTokenObserver from '@src/app/observers/ApiTokenObserver';
import Model from '@src/core/base/Model';
import IApiTokenModel, { IApiTokenData } from '@src/core/domains/auth/interfaces/IApitokenModel';
import IUserModel from '@src/core/domains/auth/interfaces/IUserModel';
import Scopes from '@src/core/domains/auth/services/Scopes';

/**
* ApiToken model
Expand All @@ -20,9 +22,26 @@ class ApiToken extends Model<IApiTokenData> implements IApiTokenModel {
public fields: string[] = [
'userId',
'token',
'scopes',
'revokedAt'
]

public json: string[] = [
'scopes'
]

/**
* Construct an ApiToken model from the given data.
*
* @param {IApiTokenData} [data=null] The data to construct the model from.
*
* @constructor
*/
constructor(data: IApiTokenData | null = null) {
super(data)
this.observeWith(ApiTokenObserver)
}

/**
* Disable createdAt and updatedAt timestamps
*/
Expand All @@ -39,6 +58,21 @@ class ApiToken extends Model<IApiTokenData> implements IApiTokenModel {
})
}

/**
* Checks if the given scope(s) are present in the scopes of this ApiToken
* @param scopes The scope(s) to check
* @returns True if all scopes are present, false otherwise
*/
public hasScope(scopes: string | string[], exactMatch: boolean = true): boolean {
const currentScopes = this.getAttribute('scopes') ?? [];

if(exactMatch) {
return Scopes.exactMatch(currentScopes, scopes);
}

return Scopes.partialMatch(currentScopes, scopes);
}

}

export default ApiToken
56 changes: 54 additions & 2 deletions src/app/models/auth/User.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
import ApiToken from "@src/app/models/auth/ApiToken";
import UserObserver from "@src/app/observers/UserObserver";
import Model from "@src/core/base/Model";
import IUserModel, { IUserData } from "@src/core/domains/auth/interfaces/IUserModel";
import IUserModel from "@src/core/domains/auth/interfaces/IUserModel";
import IModelAttributes from "@src/core/interfaces/IModelData";

/**
* User structure
*/
export interface IUserData extends IModelAttributes {
email: string;
password?: string;
hashedPassword: string;
roles: string[];
groups: string[];
firstName?: string;
lastName?: string;
createdAt?: Date;
updatedAt?: Date;
}

/**
* User model
Expand Down Expand Up @@ -31,7 +47,8 @@ export default class User extends Model<IUserData> implements IUserModel {
guarded: string[] = [
'hashedPassword',
'password',
'roles'
'roles',
'groups',
];

/**
Expand All @@ -56,9 +73,44 @@ export default class User extends Model<IUserData> implements IUserModel {
* These fields will be returned as JSON when the model is serialized.
*/
json = [
'groups',
'roles'
]

/**
* Checks if the user has the given role
*
* @param role The role to check
* @returns True if the user has the role, false otherwise
*/
hasRole(roles: string | string[]): boolean {
roles = typeof roles === 'string' ? [roles] : roles;
const userRoles = this.getAttribute('roles') ?? [];

for(const role of roles) {
if(!userRoles.includes(role)) return false;
}

return true;
}

/**
* Checks if the user has the given role
*
* @param role The role to check
* @returns True if the user has the role, false otherwise
*/
hasGroup(groups: string | string[]): boolean {
groups = typeof groups === 'string' ? [groups] : groups;
const userGroups = this.getAttribute('groups') ?? [];

for(const group of groups) {
if(!userGroups.includes(group)) return false;
}

return true;
}

/**
* @returns The tokens associated with this user
*
Expand Down
53 changes: 53 additions & 0 deletions src/app/observers/ApiTokenObserver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import UserRepository from "@src/app/repositories/auth/UserRepository";
import { IApiTokenData } from "@src/core/domains/auth/interfaces/IApitokenModel";
import IUserModel from "@src/core/domains/auth/interfaces/IUserModel";
import Observer from "@src/core/domains/observer/services/Observer";
import { App } from "@src/core/services/App";

interface IApiTokenObserverData extends IApiTokenData {

}

export default class ApiTokenObserver extends Observer<IApiTokenObserverData> {

protected readonly userRepository = new UserRepository();

/**
* Called when a data object is being created.
* @param data The model data being created.
* @returns The processed model data.
*/
async creating(data: IApiTokenObserverData): Promise<IApiTokenObserverData> {
data = await this.addGroupScopes(data)
return data
}

/**
* Adds scopes from groups the user is a member of to the scopes of the ApiToken being created.
* @param data The ApiToken data being created.
* @returns The ApiToken data with the added scopes.
*/

async addGroupScopes(data: IApiTokenObserverData): Promise<IApiTokenObserverData> {
const user = await this.userRepository.findById(data.userId) as IUserModel;

if(!user) {
return data
}

const userGroups = user.getAttribute('groups') ?? [];

for(const userGroup of userGroups) {
const group = App.container('auth').config.permissions.groups.find(g => g.name === userGroup);
const scopes = group?.scopes ?? [];

data.scopes = [
...data.scopes,
...scopes
]
}

return data
}

}
Loading