-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New Feature - Authentication Middleware Injection #123
base: main
Are you sure you want to change the base?
Changes from 3 commits
e9e538d
0ecb6dc
e72b37b
bd711c7
937668e
96ca247
6032875
35b6fc6
ab8debb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -12,3 +12,4 @@ LOG_LEVEL= | |
PORT=3030 | ||
UPLOAD_LIMIT='' | ||
PLURALIZE_SCHEMAS_ENABLED= | ||
ALLOWED_ORIGINS= |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { NextFunction, Request, Response } from 'express'; | ||
|
||
import { UserSession } from '@overture-stack/lyric'; | ||
|
||
// Extend the Request interface to include a `user` property | ||
declare module 'express-serve-static-core' { | ||
interface Request { | ||
user?: UserSession; | ||
} | ||
} | ||
|
||
export const authMiddleware = (req: Request, res: Response, next: NextFunction) => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
// Middleware for implementing authentication logic. | ||
|
||
req.user = { | ||
username: 'Guest', // Example: Adjust fields as per your `UserSession` type | ||
}; | ||
next(); // Continue to the next middleware or route handler | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,40 @@ | ||
import cors from 'cors'; | ||
import express from 'express'; | ||
import helmet from 'helmet'; | ||
import { serve, setup } from 'swagger-ui-express'; | ||
|
||
import { errorHandler, provider } from '@overture-stack/lyric'; | ||
|
||
import { defaultAppConfig, getServerConfig } from './config/server.js'; | ||
import { appConfig, getServerConfig } from './config/app.js'; | ||
import swaggerDoc from './config/swagger.js'; | ||
import healthRouter from './routes/health.js'; | ||
import pingRouter from './routes/ping.js'; | ||
|
||
const serverConfig = getServerConfig(); | ||
const { allowedOrigins, port } = getServerConfig(); | ||
|
||
const lyricProvider = provider(defaultAppConfig); | ||
const lyricProvider = provider(appConfig); | ||
|
||
// Create Express server | ||
const app = express(); | ||
|
||
app.use(helmet()); | ||
|
||
app.use( | ||
cors({ | ||
origin: function (origin, callback) { | ||
if (!origin) { | ||
// allow requests with no origin | ||
// (like mobile apps or curl requests) | ||
return callback(null, true); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do we want to add a feature flag (env var config) to enable this, and have it disabled by default? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for noticing this. In regards to security this should be disabled by default. |
||
} else if (allowedOrigins && allowedOrigins.split(',').indexOf(origin) !== -1) { | ||
return callback(null, true); | ||
} | ||
const msg = 'The CORS policy for this site does not allow access from the specified Origin.'; | ||
return callback(new Error(msg), false); | ||
}, | ||
}), | ||
); | ||
|
||
// Ping Route | ||
app.use('/ping', pingRouter); | ||
|
||
|
@@ -35,6 +52,6 @@ app.use('/health', healthRouter); | |
|
||
app.use(errorHandler); | ||
// running the server | ||
app.listen(serverConfig.port, () => { | ||
console.log(`Starting Express server on http://localhost:${serverConfig.port}`); | ||
app.listen(port, () => { | ||
console.log(`Starting Express server on http://localhost:${port}`); | ||
justincorrigible marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}); |
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Updating Docs. Adding section to implement There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. those examples are really helpful! wonder if it would be more useful for integrators to have those as actual files instead (like a ready to copy and customize scaffolding) thoughts? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think providing our database migration script allows integrators to ensure Lyric will keep track of codebase and database schema in sync. For example, integrators over time can jump to latest version of Lyric, and they will be sure the database schema will be updated as well. In essence, how Lyric stores data shouldn't be a headache for implementers. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,56 +8,145 @@ | |
npm i @overture-stack/lyric | ||
``` | ||
|
||
## Usage | ||
## Configuration | ||
|
||
### Provider | ||
|
||
Import `AppConfig` and `provider` from `@overture-stack/lyric` module to initialize the provider with custom configuration: | ||
|
||
``` | ||
```javascript | ||
import { AppConfig, provider } from '@overture-stack/lyric'; | ||
|
||
const appConfig: AppConfig = { | ||
db: { | ||
host: [INSERT_DB_HOST], | ||
port: [INSERT_DB_PORT], | ||
database: [INSERT_DB_NAME], | ||
user:[INSERT_DB_USER], | ||
password: [INSERT_DB_PASSWORD], | ||
host: 'localhost', // Database hostname or IP address | ||
port: 5432, // Database port | ||
database: 'my_database', // Name of the database | ||
user: 'db_user', // Username for database authentication | ||
password: 'secure_password', // Password for database authentication | ||
}, | ||
features: { | ||
audit: { | ||
enabled: [INSERT_AUDIT_ENABLED] | ||
} | ||
enabled: true, // Enable audit functionality (true/false) | ||
}, | ||
recordHierarchy: { | ||
pluralizeSchemasName: false, // Enable or disable automatic schema name pluralization (true/false) | ||
}, | ||
}, | ||
schemaService: { | ||
url: [INSERT_LECTERN_URL], | ||
idService: { | ||
useLocal: true, // Use local ID generation (true/false) | ||
customAlphabet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890', // Custom alphabet for ID generation | ||
customSize: 12, // Size of the generated ID | ||
}, | ||
limits: { | ||
fileSize: [INSERT_UPLOAD_LIMIT], | ||
fileSize: 10485760, // Maximum file size in bytes (e.g., 10MB = 10 * 1024 * 1024) | ||
}, | ||
logger: { | ||
level: [INSERT_LOG_LEVEL], | ||
level: 'info', // Logging level (e.g., 'debug', 'info', 'warn', 'error') | ||
}, | ||
schemaService: { | ||
url: 'https://api.lectern-service.com', // URL of the schema service | ||
}, | ||
}; | ||
|
||
|
||
const lyricProvider = provider(appConfig); | ||
``` | ||
|
||
Use any of the resources available on provider on a Express server: | ||
### Auth Middleware | ||
|
||
An authentication middleware checks whether the incoming requests are authorized before passing them to the next handler. | ||
|
||
A `UserSession` type is a structured object that contains details about a logged-in user, which should be attached to the request object. | ||
|
||
Implement an Express middleware to validate the token and attach the user session to `req.user`. | ||
|
||
- Import a router: | ||
```javascript | ||
import { Request, Response, NextFunction } from 'express'; | ||
import { UserSession } from '@overture-stack/lyric'; | ||
import jwt from 'jsonwebtoken'; | ||
|
||
const authMiddleware = (req: Request, res: Response, next: NextFunction): void => { | ||
// Extract the token from the request header | ||
const authHeader = req.headers['authorization']; | ||
|
||
// Check if the Authorization header exists and starts with "Bearer" | ||
if (!authHeader || !authHeader.startsWith('Bearer ')) { | ||
return res.status(401).json({ message: 'Unauthorized: No token provided' }); | ||
} | ||
|
||
// Extract the token by removing the "Bearer " prefix | ||
const token = authHeader.split(' ')[1]; | ||
|
||
try { | ||
// Verify the token using a public key | ||
const publicKey = process.env.JWT_PUBLIC_KEY!; | ||
const decodedToken = jwt.verify(token, publicKey); | ||
|
||
// Attach the UserSession properties to the request object | ||
req.user = { | ||
username: decodedToken.username, // Example: Adjust fields as per your `UserSession` type | ||
}; | ||
|
||
next(); // Continue to the next middleware or route handler | ||
} catch (err) { | ||
return res.status(403).json({ message: 'Forbidden: Invalid token' }); | ||
} | ||
|
||
// If valid, proceed to the next middleware or route handler | ||
next(); | ||
}; | ||
``` | ||
|
||
Add the middleware to your `AppConfig` object. | ||
|
||
```javascript | ||
import { AppConfig, provider, UserSession } from '@overture-stack/lyric'; | ||
|
||
const appConfig: AppConfig = { | ||
...// Other configuration | ||
{ | ||
authMiddleware?: authMiddleware; | ||
} | ||
``` | ||
|
||
## Usage | ||
|
||
### Express Routers | ||
|
||
Use any of the resources available on provider on a Express server: | ||
|
||
```javascript | ||
import express from 'express'; | ||
|
||
const app = express(); | ||
|
||
app.use('/submission', lyricProvider.routers.submission); | ||
``` | ||
|
||
### Database Migrations | ||
|
||
Import `migrate` function from `@overture-stack/lyric` module to run Database migrations | ||
|
||
```javascript | ||
import { migrate } from '@overture-stack/lyric'; | ||
|
||
migrate({ | ||
host: 'localhost', // Database hostname or IP address | ||
port: 5432, // Database port | ||
database: 'my_database', // Name of the database | ||
user: 'db_user', // Username for database authentication | ||
password: 'secure_password', // Password for database authentication | ||
}); | ||
``` | ||
|
||
## Support & Contributions | ||
|
||
- Developer documentation [docs](https://github.com/overture-stack/lyric/blob/main/packages/data-provider/docs/add-new-resources.md) | ||
- Filing an [issue](https://github.com/overture-stack/lyric/issues) | ||
- Connect with us on [Slack](http://slack.overture.bio) | ||
- Add or Upvote a [feature request](https://github.com/overture-stack/lyric/issues/new?assignees=&labels=&projects=&template=Feature_Requests.md) | ||
|
||
``` | ||
|
||
``` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code block looks unused? |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
import { NodePgDatabase } from 'drizzle-orm/node-postgres'; | ||
import { NextFunction, Request, Response } from 'express'; | ||
|
||
import type { DbConfig } from '@overture-stack/lyric-data-model'; | ||
import * as schema from '@overture-stack/lyric-data-model/models'; | ||
|
@@ -48,6 +49,7 @@ export type AppConfig = { | |
limits: LimitsConfig; | ||
logger: LoggerConfig; | ||
schemaService: SchemaServiceConfig; | ||
authMiddleware?: (req: Request, res: Response, next: NextFunction) => void; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Applications that implements Lyric provider will pass a custom middleware for authentication |
||
}; | ||
|
||
/** | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Example value could be helpful here