Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ legacy
docs
backups
**/*.dump
.pnpm-store
.planning
11 changes: 11 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ JWT_SECRET=change-me-jwt-secret
# Never reuse JWT_SECRET in a real deployment.
TERMINAL_QR_SECRET=change-me-independent-terminal-qr-secret

# Independent secrets for optional terminal PIN and signed audit events.
# Generate each separately (at least 32 characters); never reuse other secrets.
# Leaving these absent preserves QR-only operation and disables PIN activation.
# TERMINAL_PIN_PEPPER=<independent-random-secret>
TERMINAL_PIN_PEPPER_VERSION=1
# TERMINAL_PIN_PREVIOUS_PEPPERS={"old-version":"old-independent-secret"}
# TERMINAL_AUDIT_KEY=<another-independent-random-secret>
TERMINAL_AUDIT_KEY_VERSION=1
# Export/verification tool only: retain historical keys for audit verification.
# TERMINAL_AUDIT_PREVIOUS_KEYS={"old-version":"old-audit-secret"}

# Short-lived QR challenge: 30–60 seconds. Pairing code: 60–1800 seconds.
TERMINAL_CHALLENGE_TTL_SECONDS=45
TERMINAL_PAIRING_TTL_SECONDS=600
Expand Down
11 changes: 10 additions & 1 deletion .env.prod.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

# Pin deployments to an immutable OpenClockwork release. Do not use `latest`
# for production upgrades.
OPENCLOCKWORK_VERSION=2.0.1
OPENCLOCKWORK_VERSION=2.1.0
# Keep these names unchanged across upgrades.
OPENCLOCKWORK_DB_VOLUME=openclockwork-db-data-prod
OPENCLOCKWORK_ATTACHMENTS_VOLUME=openclockwork-attachments-prod
Expand All @@ -19,6 +19,15 @@ JWT_SECRET=change-me-long-random-jwt-secret
TERMINAL_QR_SECRET=change-me-independent-terminal-qr-secret
TERMINAL_CHALLENGE_TTL_SECONDS=45
TERMINAL_PAIRING_TTL_SECONDS=600
# Optional PIN pilot: use docker-compose.terminal-pin.yml with separate roles.
# DATABASE_URL must then select openclockwork_runtime, not the migration owner.
# MIGRATION_DATABASE_URL=postgresql://<owner>:<password>@db:5432/openclockwork?schema=public
# TERMINAL_RUNTIME_DB_PASSWORD=<independent-runtime-password-at-least-20-characters>
# TERMINAL_PIN_PEPPER=<independent-random-secret-at-least-32-characters>
# TERMINAL_PIN_PEPPER_VERSION=1
# TERMINAL_PIN_PREVIOUS_PEPPERS={"old-version":"old-pin-pepper"}
# TERMINAL_AUDIT_KEY=<another-independent-random-secret-at-least-32-characters>
# TERMINAL_AUDIT_KEY_VERSION=1
ERP_API_KEY=change-me-erp-api-key
CRON_API_KEY=change-me-cron-api-key
API_CORS_ORIGINS=https://time.example.com
Expand Down
39 changes: 39 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@ jobs:
- name: Apply migrations to test DB
run: pnpm prisma migrate deploy

- name: Verify generated API contract
run: pnpm verify:api

- name: API end-to-end tests
run: pnpm nx e2e api-e2e

Expand Down Expand Up @@ -240,3 +243,39 @@ jobs:

- name: Verify existing data survived the update
run: node ops/upgrade-smoke.mjs verify

terminal-pin-browser:
runs-on: ubuntu-latest
timeout-minutes: 25
env:
BASE_URL: http://localhost:18082
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
with:
version: 9.15.9
- uses: actions/setup-node@v6
with:
node-version: 20
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm prisma generate
- name: Install Chromium
run: pnpm exec playwright install --with-deps chromium
- name: Build and start isolated PIN stack
run: docker compose -p openclockwork-pin-test -f docker-compose.pin-test.yml up -d --build --wait
- name: Seed synthetic Team pilot
run: >-
docker compose -p openclockwork-pin-test -f docker-compose.pin-test.yml run --rm
-e NODE_ENV=development
-e OPENCLOCKWORK_SEED_CONFIRM_DATABASE=openclockwork_test_pin
migrate node --import tsx prisma/seed.ts
- name: Verify real device/PIN/browser contract
run: pnpm nx e2e web-e2e --args="--project=chromium --grep='Terminal PIN'"
- name: Start three actual API processes
run: docker compose -p openclockwork-pin-test -f docker-compose.pin-test.yml up -d --no-build --scale api=3 --wait
- name: Verify shared lockout, restart and parallel grant booking
run: pnpm nx run api-e2e:test-terminal-runtime
- name: Stop pilot
if: always()
run: docker compose -p openclockwork-pin-test -f docker-compose.pin-test.yml down
3 changes: 3 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ jobs:
- name: Apply release migrations to a fresh database
run: pnpm prisma migrate deploy

- name: Verify generated API contract
run: pnpm verify:api

- name: Run API end-to-end tests
run: pnpm nx e2e api-e2e

Expand Down
4 changes: 3 additions & 1 deletion Dockerfile.api
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ COPY libs ./libs
COPY prisma ./prisma
COPY ops/db-target-safety.cjs ./ops/db-target-safety.cjs
COPY ops/safe-db-reset.mjs ./ops/safe-db-reset.mjs
COPY ops/configure-terminal-runtime.mjs ops/terminal-audit.mjs ops/terminal-audit-chain.mjs ./ops/

# Generate Prisma client + build the API bundle.
RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \
Expand Down Expand Up @@ -47,11 +48,12 @@ COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./package.json
COPY --from=builder /app/ops/db-target-safety.cjs ./ops/db-target-safety.cjs
COPY --from=builder /app/ops/safe-db-reset.mjs ./ops/safe-db-reset.mjs
COPY --from=builder /app/ops/configure-terminal-runtime.mjs /app/ops/terminal-audit.mjs /app/ops/terminal-audit-chain.mjs ./ops/

# Seed the mount point with app-user ownership. Docker copies this metadata
# into a newly created named volume so local attachments stay writable after
# container replacement.
RUN mkdir -p /app/data/attachments && chown -R app:app /app/data
RUN mkdir -p /app/data/attachments /app/data/audit && chown -R app:app /app/data

USER app
EXPOSE 3000
Expand Down
58 changes: 45 additions & 13 deletions FEATURES.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
# OpenClockwork Features

OpenClockwork 2.0.1 provides **Solo** personal work tracking and **Team** time and
OpenClockwork 2.1.0 provides **Solo** personal work tracking and **Team** time and
attendance in one responsive, self-hosted application. Both keep deployment,
data, and configuration under the operator's control, with German and English UI.

Version 2.1.0 adds direct PIN identification and explicit time booking at Team
tablets, HR PIN administration, and an authentication audit. It retains the
personnel-data access restrictions introduced in 2.0.1. RFID is planned separately.

This page describes implemented capabilities, not a guarantee that every rule,
device, or external browser handoff has been certified. See
[release notes](RELEASE_NOTES.md) for release-specific validation and
Expand All @@ -28,14 +32,14 @@ Invoices, rates, payments, and CAUR coding-agent usage import/accounting are

## At a glance

| Audience | Main capabilities |
| ----------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| Solo owner | Personal timer, direct corrections, calendar, customers, billable projects/orders, timesheets, optional effective-dated accounts |
| Employees | Clock in/out, scan tablet QR codes, book daily targets, manage requests and absences, view calendars and time accounts |
| Managers | Review team requests, handle substitute workflows, approve or return corrections, use bulk actions, inspect project data |
| HR administrators | Manage employees, schedules, leave, projects, reports, terminal kiosks, geofences, devices, and production bootstrap |
| Kiosk devices | Pair once, display a branded rotating challenge, refresh automatically, report health, and operate with least-privilege credentials |
| Integrations | Consume documented REST endpoints, generated types, realtime events, ERP exports, and health checks |
| Audience | Main capabilities |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| Solo owner | Personal timer, direct corrections, calendar, customers, billable projects/orders, timesheets, optional effective-dated accounts |
| Employees | Clock in/out, use tablet QR or PIN, book daily targets, manage requests and absences, view calendars and time accounts |
| Managers | Review team requests, handle substitute workflows, approve or return corrections, use bulk actions, inspect project data |
| HR administrators | Manage employees, schedules, leave, projects, reports, terminal methods and PINs, authentication audits, devices, and bootstrap |
| Kiosk devices | Pair once, offer configured QR/PIN methods, request explicit IN/OUT, clear personal data, and use least-privilege device credentials |
| Integrations | Consume documented REST endpoints, generated types, realtime events, ERP exports, and health checks |

Employee, manager, HR, kiosk, and ERP capabilities in the table refer to Team.
Solo exposes only the owner's authorised personal endpoints; old Team routes
Expand Down Expand Up @@ -83,7 +87,7 @@ contains effective work or time off, the accounting mode starts on the next
working-timezone calendar date. See [the mode guide](docs/OPERATING_MODES.md).

Solo customer management and personal billable reports are not Team screens in
2.0. Preserving records across a switch does not make every mode-specific
2.1.0. Preserving records across a switch does not make every mode-specific
workflow available in both modes.

## Mobile PWA
Expand Down Expand Up @@ -119,7 +123,7 @@ using an embedded browser surface.

The screenshots on this page show the Team and kiosk workflows.

## QR tablet terminals (Team)
## QR/PIN tablet terminals (Team)

OpenClockwork turns a standard tablet into a shared, branded time-clock display
without requiring RFID badges, proprietary readers, or biometrics. The mounted
Expand All @@ -131,15 +135,29 @@ device never acts as an employee and cannot call normal HR or time-entry APIs.

| Capability | What it provides |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| Terminal configuration | Internal name, visible location, custom message, optional PNG/JPEG/WebP logo, IANA time zone, and active state |
| Terminal configuration | Internal name, visible location, custom message, optional PNG/JPEG/WebP logo, IANA time zone, active state, and QR/PIN/both methods |
| Optional GPS | Run without any geofence, or require a fresh position inside a configured radius and maximum accuracy |
| One-time pairing | Short-lived code and kiosk URL; a new pairing revokes the previous active device |
| Kiosk PWA | Dedicated manifest and full-screen view with time, date, location, custom branding, connection state, and automatic challenge refresh |
| Employee scanner | Explicit clock-in or clock-out choice, camera preview, local QR decoding, and GPS only when the terminal requires it |
| Device operations | Last-seen monitoring, re-pairing, immediate device revocation, terminal deactivation, and permanent deletion |
| Time-zone handling | IANA selection with the browser timezone suggested, UTC fallback, and server-side validation |
| PIN identification | Personal number plus separate eight-digit PIN; explicit IN/OUT, shared lockout, short-lived device-bound permission |
| HR PIN management | Generate a random PIN shown once, reset, disable, and unlock; no employee password reuse |
| Shared-screen privacy | Minimal confirmation identity, no employee session, and cleanup after completion, cancellation, inactivity, or background changes |
| Booking confirmation | Explicit clock-in/out after identification; an uncertain response can retry the same operation without creating a second booking |
| Authentication audit | HR-filtered events, restricted runtime role, signed chain, and independent-anchor export/verification tooling |
| Durable audit trail | Historical bookings retain terminal/GPS evidence snapshots even when kiosk-only records are removed |

QR/PIN terminals are included in 2.1.0 and available only in Team mode. Existing
terminals remain QR-only after upgrade until HR changes their configuration.
PIN is optional and requires independent secrets and restricted runtime DB rights.
[PIN operations](docs/TERMINAL_AUTH_OPERATIONS.de.md) documents setup, rotation,
retention, and audit limits. Operators configure and administer the independent
archive; the local file adapter does not provide a WORM guarantee. No PINs,
personal numbers, or booking grants are included in authentication audit events.
RFID readers and badge management remain [planned](ROADMAP.md#4-optional-rfid-terminal-extension).

The local iPad pilot includes a trusted-HTTPS Compose overlay, generated test
CA/server certificates, loopback-only raw service ports, an Nginx TLS gateway,
Guided Access guidance, a site acceptance checklist, and MDM recommendations.
Expand All @@ -152,6 +170,7 @@ See [the German iPad setup guide](docs/IPAD_TERMINAL_SETUP.de.md).
| Personal dashboard | Leave balance, overtime account, current booking, open requests, and detected core-time violations |
| Clock in and out | PWA booking with optional GPS, project/service-order selection, and recent booking history |
| QR terminal booking | Scan a short-lived kiosk challenge and let the server validate identity, replay state, and optional geofence |
| PIN terminal booking | Enter the exact personal number and separate PIN at the tablet, then explicitly confirm clock-in or clock-out |
| Direct daily-target block | With per-employee HR permission, book the contractual daily target as one completed and directly approved block |
| Core-time violation details | Date, affected core window, violation type, boundary, and uncovered minutes |
| Project and service-order booking | Book only to assigned active projects; active service orders become the required booking level |
Expand Down Expand Up @@ -289,13 +308,21 @@ local rules and calendar coverage for each period in use.
- JWT access/refresh authentication for interactive users
- Employee identity taken from the authenticated token for live booking APIs
- Role-based guards for employee, manager, and HR endpoints
- Personnel/account access scoped to self, managed team, or HR; colleague
selectors use an authenticated names-only directory
- Dedicated API-key protection for machine-to-machine ERP exports
- Authenticated Socket.IO connections
- Password hashing and refresh-token rotation
- Session-version invalidation after password changes or owner recovery
- Owner-only Solo access and stale-revision checks on personal mutations
- Separate `TERMINAL_QR_SECRET` for kiosk pairing and QR signing material
- Hash-only device credential storage and immediate revocation
- Separate Argon2id terminal PINs, shared attempt limits and lockouts, and
device-bound booking permissions invalidated by credential/configuration changes
- Explicit PIN booking actions and transactional duplicate protection; shared
tablets do not receive employee access or refresh tokens
- Authentication audit with a signed chain, restricted database writes, and
export/verification tools; external archive and retention remain operator setup
- Short-lived challenges, daily key rotation, rate limiting, expiry, and
per-employee replay protection
- Optional server-enforced geofences with maximum-accuracy validation
Expand All @@ -315,6 +342,8 @@ The generated and committed OpenAPI specification lives at

- Authenticated employee clock-in/out and terminal scanning
- HR terminal/device lifecycle endpoints and least-privilege kiosk endpoints
- HR PIN reset/disable/unlock and paginated authentication audit; device-only
challenge, PIN identification, and explicit booking endpoints
- Employee daily-target capability and direct-block creation
- Paginated ERP time-entry exports with project, service-order, and activity
references
Expand All @@ -341,6 +370,8 @@ in this release.
- Azure Container Apps, ACR, PostgreSQL Flexible Server, Key Vault, Blob
Storage, and Log Analytics reference infrastructure
- Dedicated terminal QR secret in Compose and Azure Key Vault
- Optional PIN Compose overlay separating database migration and runtime roles,
independent PIN/audit secrets, and a separate local QR/PIN test stack
- Stable versioned images and documented backup/migration/rollback procedures
- Nx targets for lint, type-check, build, unit, integration, E2E, and browser
tests
Expand All @@ -359,8 +390,9 @@ installation identity is sent back to OpenClockwork.
- [Choose an operating mode](docs/OPERATING_MODES.md)
- [Use Solo mode](docs/SOLO_MODE.md)
- [Use Team mode](docs/TEAM_MODE.md)
- [Roadmap: billing, agent usage, and internationalisation](ROADMAP.md)
- [Roadmap: delivered PIN, planned RFID, billing, and agent usage](ROADMAP.md)
- [Set up and test an iPad terminal](docs/IPAD_TERMINAL_SETUP.de.md)
- [Operate PIN terminals and verify their audit](docs/TERMINAL_AUTH_OPERATIONS.de.md)
- [Upgrade an existing installation](UPGRADING.md)
- [Review published releases](https://github.com/patrickschiller/openclockwork/releases)
- [Contribute with DCO sign-off](CONTRIBUTING.md)
Expand Down
Loading
Loading