mirror of
https://github.com/papra-hq/papra.git
synced 2025-12-17 12:15:22 -06:00
Compare commits
7 Commits
@papra/app
...
@papra/app
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1bfdb8aa66 | ||
|
|
2e2bb6fbbd | ||
|
|
d09b9ed70d | ||
|
|
e1571d2b87 | ||
|
|
c9a66e4aa8 | ||
|
|
9fa2df4235 | ||
|
|
c84a921988 |
@@ -39,7 +39,7 @@ By integrating Papra with OwlRelay, your instance will generate email addresses
|
||||
|
||||
3. **Configure your Papra instance**
|
||||
|
||||
Once you have created your API key, you can configure your Papra instance to receive emails by setting the `OWLRELAY_API_KEY` and `OWLRELAY_WEBHOOK_SECRET` environment variables.
|
||||
Once you have created your API key, you can configure your Papra instance to receive emails by setting the `OWLRELAY_API_KEY` and `INTAKE_EMAILS_WEBHOOK_SECRET` environment variables.
|
||||
|
||||
```bash
|
||||
# Enable intake emails
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# @papra/app-client
|
||||
|
||||
## 0.9.6
|
||||
|
||||
## 0.9.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@papra/app-client",
|
||||
"type": "module",
|
||||
"version": "0.9.5",
|
||||
"version": "0.9.6",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.12.3",
|
||||
"description": "Papra frontend client",
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# @papra/app-server
|
||||
|
||||
## 0.9.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#531](https://github.com/papra-hq/papra/pull/531) [`2e2bb6f`](https://github.com/papra-hq/papra/commit/2e2bb6fbbdd02f6b8352ef2653bef0447948c1f0) Thanks [@CorentinTh](https://github.com/CorentinTh)! - Added env variable to configure ip header for rate limit
|
||||
|
||||
- [#524](https://github.com/papra-hq/papra/pull/524) [`c84a921`](https://github.com/papra-hq/papra/commit/c84a9219886ecb2a77c67d904cf8c8d15b50747b) Thanks [@CorentinTh](https://github.com/CorentinTh)! - Fixed the api validation of tag colors to make it case incensitive
|
||||
|
||||
## 0.9.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@papra/app-server",
|
||||
"type": "module",
|
||||
"version": "0.9.5",
|
||||
"version": "0.9.6",
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.12.3",
|
||||
"description": "Papra app server",
|
||||
|
||||
@@ -55,6 +55,17 @@ export const authConfig = {
|
||||
default: false,
|
||||
env: 'AUTH_SHOW_LEGAL_LINKS',
|
||||
},
|
||||
ipAddressHeaders: {
|
||||
doc: `The header, or comma separated list of headers, to use to get the real IP address of the user, use for rate limiting. Make sur to use a non-spoofable header, one set by your proxy.
|
||||
- If behind a standard proxy, you might want to set this to "x-forwarded-for".
|
||||
- If behind Cloudflare, you might want to set this to "cf-connecting-ip".`,
|
||||
schema: z.union([
|
||||
z.string(),
|
||||
z.array(z.string()),
|
||||
]).transform(value => (typeof value === 'string' ? value.split(',').map(v => v.trim()) : value)),
|
||||
default: ['x-forwarded-for'],
|
||||
env: 'AUTH_IP_ADDRESS_HEADERS',
|
||||
},
|
||||
providers: {
|
||||
email: {
|
||||
isEnabled: {
|
||||
|
||||
@@ -37,8 +37,8 @@ export function getAuth({
|
||||
trustedOrigins,
|
||||
logger: {
|
||||
disabled: false,
|
||||
log: (baseLevel, message) => {
|
||||
logger[baseLevel ?? 'info'](message);
|
||||
log: (baseLevel, message, ...args: unknown[]) => {
|
||||
logger[baseLevel ?? 'info']({ ...args }, message);
|
||||
},
|
||||
},
|
||||
emailAndPassword: {
|
||||
@@ -85,6 +85,9 @@ export function getAuth({
|
||||
advanced: {
|
||||
// Drizzle tables handle the id generation
|
||||
database: { generateId: false },
|
||||
ipAddress: {
|
||||
ipAddressHeaders: config.auth.ipAddressHeaders,
|
||||
},
|
||||
},
|
||||
socialProviders: {
|
||||
github: {
|
||||
|
||||
@@ -46,7 +46,7 @@ export async function createServer(initialDeps: Partial<GlobalDependencies> = {}
|
||||
|
||||
const app = new Hono<ServerInstanceGenerics>({ strict: true });
|
||||
|
||||
app.use(createLoggerMiddleware());
|
||||
app.use(createLoggerMiddleware({ config }));
|
||||
app.use(createCorsMiddleware({ config }));
|
||||
app.use(createTimeoutMiddleware({ config }));
|
||||
app.use(secureHeaders());
|
||||
|
||||
@@ -26,3 +26,15 @@ export function getContentLengthHeader({ headers }: { headers: Record<string, st
|
||||
|
||||
return Number(contentLengthHeaderValue);
|
||||
}
|
||||
|
||||
export function getIpFromHeaders({ context, headerNames }: { context: Context; headerNames: string[] }): string | undefined {
|
||||
for (const headerName of headerNames) {
|
||||
const headerValue = getHeader({ context, name: headerName });
|
||||
|
||||
if (!isNil(headerValue)) {
|
||||
return headerValue;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import type { Context } from '../../app/server.types';
|
||||
import type { Config } from '../../config/config.types';
|
||||
import { createMiddleware } from 'hono/factory';
|
||||
import { getHeader } from '../headers/headers.models';
|
||||
import { routePath } from 'hono/route';
|
||||
import { getHeader, getIpFromHeaders } from '../headers/headers.models';
|
||||
import { generateId } from '../random/ids';
|
||||
import { createLogger, wrapWithLoggerContext } from './logger';
|
||||
|
||||
const logger = createLogger({ namespace: 'app' });
|
||||
|
||||
export function createLoggerMiddleware() {
|
||||
export function createLoggerMiddleware({ config }: { config: Config }) {
|
||||
return createMiddleware(async (context: Context, next) => {
|
||||
const requestId = getHeader({ context, name: 'x-request-id' });
|
||||
const requestId = getHeader({ context, name: 'x-request-id' }) ?? generateId({ prefix: 'req' });
|
||||
const ip = getIpFromHeaders({ context, headerNames: config.auth.ipAddressHeaders });
|
||||
|
||||
await wrapWithLoggerContext(
|
||||
{
|
||||
requestId: requestId ?? generateId({ prefix: 'req' }),
|
||||
requestId,
|
||||
},
|
||||
async () => {
|
||||
const requestedAt = new Date();
|
||||
@@ -26,9 +29,10 @@ export function createLoggerMiddleware() {
|
||||
status: context.res.status,
|
||||
method: context.req.method,
|
||||
path: context.req.path,
|
||||
routePath: context.req.routePath,
|
||||
routePath: routePath(context),
|
||||
userAgent: getHeader({ context, name: 'User-Agent' }),
|
||||
durationMs,
|
||||
ip,
|
||||
},
|
||||
'Request completed',
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createPrefixedIdRegex } from '../shared/random/ids';
|
||||
|
||||
export const TagColorRegex = /^#[0-9a-f]{6}$/;
|
||||
export const TagColorRegex = /^#[0-9A-F]{6}$/;
|
||||
|
||||
export const tagIdPrefix = 'tag';
|
||||
export const tagIdRegex = createPrefixedIdRegex({ prefix: tagIdPrefix });
|
||||
|
||||
@@ -14,10 +14,9 @@ import { ensureUserIsInOrganization } from '../organizations/organizations.useca
|
||||
import { validateJsonBody, validateParams } from '../shared/validation/validation';
|
||||
import { createWebhookRepository } from '../webhooks/webhook.repository';
|
||||
import { deferTriggerWebhooks } from '../webhooks/webhook.usecases';
|
||||
import { TagColorRegex } from './tags.constants';
|
||||
import { createTagNotFoundError } from './tags.errors';
|
||||
import { createTagsRepository } from './tags.repository';
|
||||
import { tagIdSchema } from './tags.schemas';
|
||||
import { tagColorSchema, tagIdSchema } from './tags.schemas';
|
||||
|
||||
export function registerTagsRoutes(context: RouteDefinitionContext) {
|
||||
setupCreateNewTagRoute(context);
|
||||
@@ -38,7 +37,7 @@ function setupCreateNewTagRoute({ app, db }: RouteDefinitionContext) {
|
||||
|
||||
validateJsonBody(z.object({
|
||||
name: z.string().min(1).max(50),
|
||||
color: z.string().regex(TagColorRegex, 'Invalid Color format, must be a hex color code like #000000'),
|
||||
color: tagColorSchema,
|
||||
description: z.string().max(256).optional(),
|
||||
})),
|
||||
|
||||
@@ -95,7 +94,7 @@ function setupUpdateTagRoute({ app, db }: RouteDefinitionContext) {
|
||||
|
||||
validateJsonBody(z.object({
|
||||
name: z.string().min(1).max(64).optional(),
|
||||
color: z.string().regex(TagColorRegex, 'Invalid Color format, must be a hex color code like #000000').optional(),
|
||||
color: tagColorSchema.optional(),
|
||||
description: z.string().max(256).optional(),
|
||||
})),
|
||||
|
||||
|
||||
25
apps/papra-server/src/modules/tags/tags.schemas.test.ts
Normal file
25
apps/papra-server/src/modules/tags/tags.schemas.test.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { describe, expect, test } from 'vitest';
|
||||
import { tagColorSchema } from './tags.schemas';
|
||||
|
||||
describe('tags schemas', () => {
|
||||
describe('tagColorSchema', () => {
|
||||
test('the color of a tag is a 6 digits hex color code', () => {
|
||||
expect(() => tagColorSchema.parse('#FFFFFF')).not.toThrow();
|
||||
expect(() => tagColorSchema.parse('#000000')).not.toThrow();
|
||||
expect(() => tagColorSchema.parse('#123ABC')).not.toThrow();
|
||||
expect(() => tagColorSchema.parse('#abcdef')).not.toThrow();
|
||||
|
||||
expect(() => tagColorSchema.parse('FFFFFF')).toThrow();
|
||||
expect(() => tagColorSchema.parse('#FFF')).toThrow();
|
||||
expect(() => tagColorSchema.parse('#123ABCG')).toThrow();
|
||||
expect(() => tagColorSchema.parse('#123AB')).toThrow();
|
||||
expect(() => tagColorSchema.parse('blue')).toThrow();
|
||||
});
|
||||
|
||||
test('the color of a tag is always uppercased', () => {
|
||||
expect(tagColorSchema.parse('#abcdef')).toBe('#ABCDEF');
|
||||
expect(tagColorSchema.parse('#abCdEf')).toBe('#ABCDEF');
|
||||
expect(tagColorSchema.parse('#123abc')).toBe('#123ABC');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,5 @@
|
||||
import { z } from 'zod';
|
||||
import { tagIdRegex } from './tags.constants';
|
||||
import { TagColorRegex, tagIdRegex } from './tags.constants';
|
||||
|
||||
export const tagIdSchema = z.string().regex(tagIdRegex);
|
||||
export const tagColorSchema = z.string().toUpperCase().regex(TagColorRegex, 'Invalid Color format, must be a hex color code like #000000');
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"name": "@papra/root",
|
||||
"type": "module",
|
||||
"version": "0.3.0",
|
||||
"packageManager": "pnpm@10.12.3",
|
||||
"description": "Papra document management monorepo root",
|
||||
|
||||
Reference in New Issue
Block a user