Mist, jetzt vielleicht
This commit is contained in:
36
lib/auth.ts
Normal file
36
lib/auth.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Authentifizierungsbibliothek
|
||||
* Benutzer via Umgebungsvariable konfigurieren:
|
||||
* AUTH_USERS=user1:passwort1,user2:passwort2
|
||||
*/
|
||||
|
||||
export interface User {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export function getUsers(): User[] {
|
||||
const usersString = process.env.AUTH_USERS || '';
|
||||
if (!usersString) {
|
||||
console.warn('AUTH_USERS nicht in .env konfiguriert');
|
||||
return [];
|
||||
}
|
||||
return usersString
|
||||
.split(',')
|
||||
.map((userPair) => {
|
||||
const [username, password] = userPair.trim().split(':');
|
||||
return { username: username?.trim(), password: password?.trim() };
|
||||
})
|
||||
.filter((user) => user.username && user.password);
|
||||
}
|
||||
|
||||
export function verifyCredentials(username: string, password: string): boolean {
|
||||
const users = getUsers();
|
||||
const user = users.find(u => u.username === username);
|
||||
if (!user) return false;
|
||||
return user.password === password;
|
||||
}
|
||||
|
||||
export function isAuthEnabled(): boolean {
|
||||
return !!process.env.AUTH_USERS;
|
||||
}
|
||||
26
lib/checkAblauf.ts
Normal file
26
lib/checkAblauf.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import moment from 'moment';
|
||||
import { TabletteRaw } from '@/types/tablette';
|
||||
|
||||
const VORLAUF = parseInt(process.env.VORLAUF || '14', 10);
|
||||
|
||||
export interface AblaufResult {
|
||||
akt: number;
|
||||
until: Date;
|
||||
rtage: number;
|
||||
warn: boolean;
|
||||
}
|
||||
|
||||
export function checkAblauf(item: Pick<TabletteRaw, 'cnt' | 'pday' | 'at'>): AblaufResult {
|
||||
const now = moment();
|
||||
const atday = moment(item.at);
|
||||
const days = moment.duration(now.diff(atday)).asDays();
|
||||
const aktAnzahl = item.cnt - Math.floor(days) * item.pday;
|
||||
const reichtTage = Math.floor(aktAnzahl / item.pday);
|
||||
const rbis = now.add(reichtTage, 'day').startOf('day');
|
||||
return {
|
||||
akt: aktAnzahl,
|
||||
until: rbis.toDate(),
|
||||
rtage: reichtTage,
|
||||
warn: reichtTage <= VORLAUF,
|
||||
};
|
||||
}
|
||||
49
lib/mailService.ts
Normal file
49
lib/mailService.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import nodemailer from 'nodemailer';
|
||||
import moment from 'moment';
|
||||
import type { RowDataPacket } from 'mysql2';
|
||||
import pool from './mysql';
|
||||
import { checkAblauf } from './checkAblauf';
|
||||
|
||||
// SMTP-Konfiguration via Umgebungsvariablen (Passwort niemals hart codieren!)
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: process.env.SMTP_HOST || 'smtp.1und1.de',
|
||||
port: parseInt(process.env.SMTP_PORT || '587', 10),
|
||||
secure: false,
|
||||
auth: {
|
||||
user: process.env.SMTP_USER || '',
|
||||
pass: process.env.SMTP_PASS || '',
|
||||
},
|
||||
});
|
||||
|
||||
export async function doCheckAndMail(): Promise<string> {
|
||||
const [rows] = await pool.query<RowDataPacket[]>(
|
||||
'SELECT tab, pday, cnt, at FROM tabletten'
|
||||
);
|
||||
|
||||
let body = '';
|
||||
|
||||
for (const item of rows) {
|
||||
if (item.pday !== 0) {
|
||||
const updates = checkAblauf({ cnt: item.cnt, pday: item.pday, at: item.at });
|
||||
await pool.execute(
|
||||
'UPDATE tabletten SET akt = ?, until = ?, warn = ? WHERE tab = ?',
|
||||
[updates.akt, moment(updates.until).format('YYYY-MM-DD'), updates.warn ? 1 : 0, item.tab]
|
||||
);
|
||||
if (updates.warn) {
|
||||
const name = item.tab.split(' ')[0];
|
||||
body += `"${name}" wird am ${moment(updates.until).format('YYYY-MM-DD')} (in ${updates.rtage} Tagen) zu Ende sein\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (body) {
|
||||
await transporter.sendMail({
|
||||
from: `"Tabletten" <${process.env.SMTP_USER}>`,
|
||||
to: process.env.MAIL_TO || '',
|
||||
subject: 'Tabletten gehen zu Ende',
|
||||
text: body,
|
||||
});
|
||||
}
|
||||
|
||||
return body || 'Kein Warn-Eintrag.';
|
||||
}
|
||||
36
lib/mongodb.ts
Normal file
36
lib/mongodb.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { MongoClient, MongoClientOptions } from 'mongodb';
|
||||
|
||||
const MONGOHOST = process.env.MONGOHOST || 'localhost';
|
||||
const MONGOPORT = process.env.MONGOPORT || '27017';
|
||||
const MONGOAUTH = process.env.MONGOAUTH === 'true';
|
||||
const MONGOUSRP = process.env.MONGOUSRP || '';
|
||||
export const MONGOBASE = process.env.MONGOBASE || 'medizin';
|
||||
|
||||
const MONGO_URL = MONGOAUTH
|
||||
? `mongodb://${MONGOUSRP}@${MONGOHOST}:${MONGOPORT}/?authSource=admin`
|
||||
: `mongodb://${MONGOHOST}:${MONGOPORT}`;
|
||||
|
||||
console.log("auth:", MONGOAUTH, "url:", MONGO_URL)
|
||||
const options: MongoClientOptions = {};
|
||||
|
||||
let client: MongoClient;
|
||||
let clientPromise: Promise<MongoClient>;
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line no-var
|
||||
var _mongoClientPromise: Promise<MongoClient> | undefined;
|
||||
}
|
||||
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// In development, use a global variable to preserve the connection across HMR reloads
|
||||
if (!global._mongoClientPromise) {
|
||||
client = new MongoClient(MONGO_URL, options);
|
||||
global._mongoClientPromise = client.connect();
|
||||
}
|
||||
clientPromise = global._mongoClientPromise;
|
||||
} else {
|
||||
client = new MongoClient(MONGO_URL, options);
|
||||
clientPromise = client.connect();
|
||||
}
|
||||
|
||||
export default clientPromise;
|
||||
13
lib/mysql.ts
Normal file
13
lib/mysql.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import mysql from 'mysql2/promise';
|
||||
|
||||
const pool = mysql.createPool({
|
||||
host: process.env.DB_HOST || 'localhost',
|
||||
port: parseInt(process.env.DB_PORT || '3306', 10),
|
||||
user: process.env.DB_USER || 'root',
|
||||
password: process.env.DB_PASS || '',
|
||||
database: 'medizin',
|
||||
waitForConnections: true,
|
||||
connectionLimit: 10,
|
||||
});
|
||||
|
||||
export default pool;
|
||||
70
lib/session.ts
Normal file
70
lib/session.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { cookies } from 'next/headers';
|
||||
import { SignJWT, jwtVerify } from 'jose';
|
||||
|
||||
const SESSION_COOKIE_NAME = 'auth_session';
|
||||
const SESSION_DURATION = 7 * 24 * 60 * 60 * 1000; // 7 Tage
|
||||
|
||||
const secretKey = process.env.AUTH_SECRET || 'default-secret-change-in-production';
|
||||
const key = new TextEncoder().encode(secretKey);
|
||||
|
||||
export interface SessionData {
|
||||
username: string;
|
||||
isAuthenticated: boolean;
|
||||
expiresAt: number;
|
||||
}
|
||||
|
||||
async function encrypt(payload: SessionData): Promise<string> {
|
||||
return await new SignJWT(payload as unknown as Record<string, unknown>)
|
||||
.setProtectedHeader({ alg: 'HS256' })
|
||||
.setIssuedAt()
|
||||
.setExpirationTime(new Date(payload.expiresAt))
|
||||
.sign(key);
|
||||
}
|
||||
|
||||
async function decrypt(token: string): Promise<SessionData | null> {
|
||||
try {
|
||||
const { payload } = await jwtVerify(token, key, {
|
||||
algorithms: ['HS256'],
|
||||
});
|
||||
return {
|
||||
username: payload.username as string,
|
||||
isAuthenticated: payload.isAuthenticated as boolean,
|
||||
expiresAt: payload.expiresAt as number,
|
||||
};
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function createSession(username: string): Promise<void> {
|
||||
const expiresAt = Date.now() + SESSION_DURATION;
|
||||
const session: SessionData = { username, isAuthenticated: true, expiresAt };
|
||||
const encryptedSession = await encrypt(session);
|
||||
const cookieStore = await cookies();
|
||||
cookieStore.set(SESSION_COOKIE_NAME, encryptedSession, {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
expires: expiresAt,
|
||||
sameSite: 'lax',
|
||||
path: '/',
|
||||
});
|
||||
}
|
||||
|
||||
export async function getSession(): Promise<SessionData | null> {
|
||||
const cookieStore = await cookies();
|
||||
const cookie = cookieStore.get(SESSION_COOKIE_NAME);
|
||||
if (!cookie?.value) return null;
|
||||
const session = await decrypt(cookie.value);
|
||||
if (!session || session.expiresAt < Date.now()) return null;
|
||||
return session;
|
||||
}
|
||||
|
||||
export async function deleteSession(): Promise<void> {
|
||||
const cookieStore = await cookies();
|
||||
cookieStore.delete(SESSION_COOKIE_NAME);
|
||||
}
|
||||
|
||||
export async function isAuthenticated(): Promise<boolean> {
|
||||
const session = await getSession();
|
||||
return session?.isAuthenticated ?? false;
|
||||
}
|
||||
Reference in New Issue
Block a user