commit 973c82f3043a59fbb4c5a61ec964564786adc8e6 Author: Patrick Plate Date: Wed Jun 24 15:40:17 2026 +0200 feat(w1): maven skeleton + CI scaffold - Parent POM: de.platesoft:plate-auth-parent with ${revision} CI-friendly versioning - plate-auth-starter module: Spring Boot 4.1.0 starter deps (web, jpa, security, validation, jwt, flyway, envers) - @platesoft/auth npm package skeleton: tsup bundler, conditional exports, TypeScript strict - Gitea Actions: ci.yml (on push/PR) + release.yml (on v* tag) - distributionManagement pointing to Gitea Package Registry (Maven + npm) - Apache-2.0 LICENSE, README with quickstart, CHANGELOG, .editorconfig, .gitignore - pnpm workspace with packages/auth - Maven BUILD SUCCESS verified locally diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f6491e3 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,21 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.java] +indent_size = 4 + +[*.xml] +indent_size = 4 + +[pom.xml] +indent_size = 4 + +[Makefile] +indent_style = tab diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..1f51add --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,37 @@ +name: CI +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v4 + with: + java-version: '25' + distribution: 'temurin' + cache: 'maven' + + - uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Install pnpm + run: npm install -g pnpm + + - name: Maven build + test + run: mvn -B verify + + - name: Install npm dependencies + run: pnpm install --frozen-lockfile + + - name: Build npm packages + run: pnpm -r build + + - name: Run frontend tests + run: pnpm -r test diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml new file mode 100644 index 0000000..243cfe2 --- /dev/null +++ b/.gitea/workflows/release.yml @@ -0,0 +1,61 @@ +name: Release +on: + push: + tags: ['v*'] + +jobs: + publish-maven: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v4 + with: + java-version: '25' + distribution: 'temurin' + cache: 'maven' + + - name: Configure Maven for Gitea + run: | + mkdir -p ~/.m2 + cat > ~/.m2/settings.xml < + + + gitea + ${{ secrets.GITEA_USER }} + ${{ secrets.GITEA_TOKEN }} + + + + EOF + + - name: Publish Maven artifact + run: mvn -B -Drevision=${GITHUB_REF_NAME#v} deploy -DskipTests + + publish-npm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '22' + registry-url: 'https://git.plate-software.de/api/packages/pplate/npm/' + + - name: Install pnpm + run: npm install -g pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Set version from tag + run: pnpm -F @platesoft/auth version ${GITHUB_REF_NAME#v} --no-git-tag-version + + - name: Build + run: pnpm -F @platesoft/auth build + + - name: Publish + run: pnpm -F @platesoft/auth publish --no-git-checks + env: + NPM_CONFIG_TOKEN: ${{ secrets.GITEA_TOKEN }} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e639845 --- /dev/null +++ b/.gitignore @@ -0,0 +1,42 @@ +# Java +target/ +*.class +*.jar +*.war +*.ear +.flattened-pom.xml + +# IDE +.idea/ +*.iml +.vscode/ +.project +.classpath +.settings/ + +# OS +.DS_Store +Thumbs.db + +# Gradle (if ever) +.gradle/ +build/ + +# Node / npm +node_modules/ +dist/ +packages/auth/dist/ +*.tgz + +# Environment +.env +.env.local +.env.*.local + +# Logs +*.log +logs/ + +# Test output +test-results/ +coverage/ diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..abd0502 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,13 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [Unreleased] + +### Added +- Initial project scaffold (W1) +- Maven parent POM with `${revision}` CI-friendly versioning +- `plate-auth-starter` module skeleton +- `@platesoft/auth` npm package skeleton (tsup + ESM/CJS dual build) +- Gitea Actions CI + release pipelines +- Apache-2.0 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d604427 --- /dev/null +++ b/LICENSE @@ -0,0 +1,107 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work. + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. + + "Contribution" shall mean any work of authorship submitted to the + Licensor for inclusion in the Work. + + "Contributor" shall mean any Legal Entity on behalf of whom a + Contribution has been received by the Licensor. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + patent license to make, have made, use, offer to sell, sell, + import, and otherwise transfer the Work. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work; and + + (d) If the Work includes a "NOTICE" text file, You must include + a readable copy of the attribution notices contained within + such NOTICE file. + + 5. Submission of Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND. + + 8. Limitation of Liability. + + 9. Accepting Warranty or Additional Liability. + + Copyright 2026 Patrick Plate (plate-software.de) + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..4b95e1f --- /dev/null +++ b/README.md @@ -0,0 +1,76 @@ +# plate-auth + +Reusable authentication + multi-tenancy library for Spring Boot 4 + NextAuth v5. + +## Two artifacts, one contract + +| Artifact | Registry | Purpose | +|----------|----------|---------| +| `de.platesoft:plate-auth-starter` | Gitea Maven | Spring Boot auto-config: JWT, OAuth, memberships, invitations, access requests | +| `@platesoft/auth` | Gitea npm | NextAuth v5 config factory, HMAC exchange, proxy helpers, React hooks | + +The wire contract between them is an **HMAC-SHA256 signed exchange envelope** + **JWT bearer tokens**. + +## Quick start (5 lines) + +### Backend (Spring Boot 4) + +```xml + + de.platesoft + plate-auth-starter + 0.1.0 + +``` + +```yaml +plate: + auth: + jwt: + secret: ${PLATE_AUTH_JWT_SECRET} # ≥32 chars + exchange: + secret: ${PLATE_AUTH_EXCHANGE_SECRET} # ≥32 chars, shared with frontend +``` + +### Frontend (Next.js 15 + NextAuth v5) + +```bash +pnpm add @platesoft/auth@0.1.0 --registry=https://git.plate-software.de/api/packages/pplate/npm/ +``` + +```ts +// app/api/auth/[...nextauth]/route.ts +import NextAuth from 'next-auth'; +import { createAuthConfig } from '@platesoft/auth/config'; + +const config = createAuthConfig({ + providers: { google: { clientId: process.env.GOOGLE_CLIENT_ID!, clientSecret: process.env.GOOGLE_CLIENT_SECRET! } }, + exchange: { backendUrl: process.env.NEXT_PUBLIC_BACKEND_URL!, secret: process.env.NEXTAUTH_EXCHANGE_SECRET! }, +}); +export const { handlers, auth, signIn, signOut } = NextAuth(config); +export const { GET, POST } = handlers; +``` + +## SPI Extension Points + +| Interface | Default | Purpose | +|-----------|---------|---------| +| `OrgValidator` | `PermissiveOrgValidator` (WARN per call) | Validate `(org_type, org_id)` exists | +| `OrgDisplayNameResolver` | Returns `type:id` | Pretty-print org | +| `InvitationMailer` | Logs accept URL | Send invite emails | +| `AccessRequestMailer` | Logs notifications | Notify on access requests | +| `OnboardingHook` | No-op | First sign-in hook | + +Override any bean with `@ConditionalOnMissingBean` — register your own to replace. + +## Documentation + +Full docs live in the [plate-auth wiki](https://git.plate-software.de/pplate/plate-auth/wiki/). + +- [Architecture](https://git.plate-software.de/pplate/plate-auth/wiki/Architecture) +- [Integration Guide](https://git.plate-software.de/pplate/plate-auth/wiki/Integration-Guide) +- [Sprint 0 Plan](https://git.plate-software.de/pplate/plate-auth/wiki/Sprint-0-Plan) + +## License + +Apache-2.0 — see [LICENSE](LICENSE). diff --git a/package.json b/package.json new file mode 100644 index 0000000..f1e189b --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "plate-auth", + "private": true, + "version": "0.1.0", + "description": "plate-auth monorepo workspace root", + "scripts": { + "build": "pnpm -r build", + "test": "pnpm -r test", + "lint": "pnpm -r lint" + }, + "engines": { + "node": ">=22.0.0", + "pnpm": ">=9.0.0" + } +} diff --git a/packages/auth/package.json b/packages/auth/package.json new file mode 100644 index 0000000..104c0f7 --- /dev/null +++ b/packages/auth/package.json @@ -0,0 +1,70 @@ +{ + "name": "@platesoft/auth", + "version": "0.1.0", + "type": "module", + "description": "NextAuth v5 config factory, HMAC exchange, proxy helpers, and React hooks for plate-auth", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs", + "types": "./dist/index.d.ts" + }, + "./config": { + "import": "./dist/config/index.js", + "types": "./dist/config/index.d.ts" + }, + "./exchange": { + "import": "./dist/exchange/index.js", + "types": "./dist/exchange/index.d.ts" + }, + "./proxy": { + "import": "./dist/proxy/index.js", + "types": "./dist/proxy/index.d.ts" + }, + "./middleware": { + "import": "./dist/middleware/index.js", + "types": "./dist/middleware/index.d.ts" + }, + "./client": { + "import": "./dist/client/index.js", + "types": "./dist/client/index.d.ts" + } + }, + "files": [ + "dist", + "README.md", + "LICENSE" + ], + "scripts": { + "build": "tsup", + "test": "vitest run", + "lint": "tsc --noEmit" + }, + "peerDependencies": { + "next": ">=15.0.0", + "next-auth": ">=5.0.0-beta", + "react": ">=19.0.0" + }, + "devDependencies": { + "next": "^15.3.0", + "next-auth": "^5.0.0-beta.30", + "react": "^19.0.0", + "tsup": "^8.4.0", + "typescript": "^5.7.0", + "vitest": "^3.1.0", + "@types/node": "^22.0.0", + "@types/react": "^19.0.0" + }, + "publishConfig": { + "registry": "https://git.plate-software.de/api/packages/pplate/npm/" + }, + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://git.plate-software.de/pplate/plate-auth.git", + "directory": "packages/auth" + } +} diff --git a/packages/auth/src/client/index.ts b/packages/auth/src/client/index.ts new file mode 100644 index 0000000..a3ba34f --- /dev/null +++ b/packages/auth/src/client/index.ts @@ -0,0 +1,17 @@ +// Placeholder — W3 implementation +// Re-exports from next-auth/react will be added in W3 +export function useAccessToken(): string | null { + throw new Error('Not yet implemented — Sprint 0 W3'); +} + +export interface Membership { + id: string; + orgType: string; + orgId: string; + role: string; + status: string; +} + +export function useMemberships(): Membership[] { + throw new Error('Not yet implemented — Sprint 0 W3'); +} diff --git a/packages/auth/src/config/index.ts b/packages/auth/src/config/index.ts new file mode 100644 index 0000000..6543c28 --- /dev/null +++ b/packages/auth/src/config/index.ts @@ -0,0 +1,25 @@ +// Placeholder — W3 implementation +export interface PlateAuthConfigOptions { + providers: { + google?: { clientId: string; clientSecret: string }; + microsoft?: { clientId: string; clientSecret: string; tenantId?: string }; + email?: { server: string; from: string }; + }; + exchange: { + backendUrl: string; + secret: string; + appLabel?: string; + }; + session?: { + strategy?: 'jwt'; + maxAge?: number; + }; + callbacks?: { + afterSignIn?: (user: any) => Promise; + }; + trustHost?: boolean; +} + +export function createAuthConfig(_opts: PlateAuthConfigOptions): any { + throw new Error('Not yet implemented — Sprint 0 W3'); +} diff --git a/packages/auth/src/exchange/index.ts b/packages/auth/src/exchange/index.ts new file mode 100644 index 0000000..580d68f --- /dev/null +++ b/packages/auth/src/exchange/index.ts @@ -0,0 +1,23 @@ +// Placeholder — W3 implementation +export interface ExchangeEnvelope { + provider: 'google' | 'microsoft' | 'email' | 'password'; + providerSubject: string; + email: string; + name?: string; + inviteToken?: string; + nonce: string; + iat: number; +} + +export interface TokenResponse { + accessToken: string; + refreshToken: string; +} + +export function signEnvelope(_envelope: ExchangeEnvelope, _secret: string): { envelope: string; signature: string } { + throw new Error('Not yet implemented — Sprint 0 W3'); +} + +export function makeNonce(): string { + return crypto.randomUUID(); +} diff --git a/packages/auth/src/index.ts b/packages/auth/src/index.ts new file mode 100644 index 0000000..f5b2f44 --- /dev/null +++ b/packages/auth/src/index.ts @@ -0,0 +1,4 @@ +// @platesoft/auth — main barrel export +export type { PlateAuthConfigOptions } from './config'; +export type { ExchangeEnvelope, TokenResponse } from './exchange'; +export type { ProxyOptions } from './proxy'; diff --git a/packages/auth/src/middleware/index.ts b/packages/auth/src/middleware/index.ts new file mode 100644 index 0000000..6601060 --- /dev/null +++ b/packages/auth/src/middleware/index.ts @@ -0,0 +1,8 @@ +// Placeholder — W3 implementation +export interface MiddlewareOptions { + publicPaths?: string[]; +} + +export function createAuthMiddleware(_opts?: MiddlewareOptions): any { + throw new Error('Not yet implemented — Sprint 0 W3'); +} diff --git a/packages/auth/src/proxy/index.ts b/packages/auth/src/proxy/index.ts new file mode 100644 index 0000000..4595be7 --- /dev/null +++ b/packages/auth/src/proxy/index.ts @@ -0,0 +1,10 @@ +// Placeholder — W3 implementation +export interface ProxyOptions { + backendUrl: string; + stripHeaders?: string[]; + authHeaderName?: string; +} + +export function createProxyHandlers(_opts: ProxyOptions): Record { + throw new Error('Not yet implemented — Sprint 0 W3'); +} diff --git a/packages/auth/tsconfig.json b/packages/auth/tsconfig.json new file mode 100644 index 0000000..069885e --- /dev/null +++ b/packages/auth/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "Bundler", + "declaration": true, + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "outDir": "./dist", + "rootDir": "./src", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "lib": ["ES2022", "DOM", "DOM.Iterable"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist", "tests"] +} diff --git a/packages/auth/tsup.config.ts b/packages/auth/tsup.config.ts new file mode 100644 index 0000000..0e0a437 --- /dev/null +++ b/packages/auth/tsup.config.ts @@ -0,0 +1,18 @@ +import { defineConfig } from 'tsup'; + +export default defineConfig({ + entry: { + 'index': 'src/index.ts', + 'config/index': 'src/config/index.ts', + 'exchange/index': 'src/exchange/index.ts', + 'proxy/index': 'src/proxy/index.ts', + 'middleware/index': 'src/middleware/index.ts', + 'client/index': 'src/client/index.ts', + }, + format: ['esm', 'cjs'], + dts: true, + splitting: true, + clean: true, + target: 'es2022', + external: ['next', 'next-auth', 'react', 'next-auth/react'], +}); diff --git a/plate-auth-starter/pom.xml b/plate-auth-starter/pom.xml new file mode 100644 index 0000000..45bcca0 --- /dev/null +++ b/plate-auth-starter/pom.xml @@ -0,0 +1,163 @@ + + + 4.0.0 + + + de.platesoft + plate-auth-parent + ${revision} + ../pom.xml + + + plate-auth-starter + plate-auth-starter + Spring Boot 4 auto-configuration starter for plate-auth (JWT, OAuth, multi-tenancy) + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-data-jpa + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-validation + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-mail + + + + + org.hibernate.orm + hibernate-envers + + + + + org.postgresql + postgresql + runtime + + + org.flywaydb + flyway-core + + + org.flywaydb + flyway-database-postgresql + + + + + io.jsonwebtoken + jjwt-api + ${jjwt.version} + + + io.jsonwebtoken + jjwt-impl + ${jjwt.version} + runtime + + + io.jsonwebtoken + jjwt-jackson + ${jjwt.version} + runtime + + + + + org.projectlombok + lombok + true + + + + + org.springframework.boot + spring-boot-configuration-processor + true + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.springframework.security + spring-security-test + test + + + com.h2database + h2 + test + + + org.testcontainers + postgresql + ${testcontainers.version} + test + + + org.testcontainers + junit-jupiter + ${testcontainers.version} + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + + + org.projectlombok + lombok + ${lombok.version} + + + org.springframework.boot + spring-boot-configuration-processor + ${project.parent.parent.version} + + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + jar-no-fork + + + + + + diff --git a/plate-auth-starter/src/main/java/de/platesoft/auth/PlateAuth.java b/plate-auth-starter/src/main/java/de/platesoft/auth/PlateAuth.java new file mode 100644 index 0000000..abe647e --- /dev/null +++ b/plate-auth-starter/src/main/java/de/platesoft/auth/PlateAuth.java @@ -0,0 +1,13 @@ +package de.platesoft.auth; + +/** + * Marker class for plate-auth-starter auto-configuration. + * Placeholder until W2 populates real classes. + */ +public final class PlateAuth { + public static final String VERSION = "0.1.0"; + + private PlateAuth() { + // utility class + } +} diff --git a/plate-auth-starter/src/test/java/de/platesoft/auth/PlateAuthTest.java b/plate-auth-starter/src/test/java/de/platesoft/auth/PlateAuthTest.java new file mode 100644 index 0000000..463f858 --- /dev/null +++ b/plate-auth-starter/src/test/java/de/platesoft/auth/PlateAuthTest.java @@ -0,0 +1,12 @@ +package de.platesoft.auth; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class PlateAuthTest { + + @Test + void versionConstant() { + assertEquals("0.1.0", PlateAuth.VERSION); + } +} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..18ec407 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - 'packages/*' diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..26bcd97 --- /dev/null +++ b/pom.xml @@ -0,0 +1,77 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 4.1.0 + + + + de.platesoft + plate-auth-parent + ${revision} + pom + + plate-auth + Reusable auth + multi-tenancy starter for Spring Boot 4 + NextAuth v5 + https://git.plate-software.de/pplate/plate-auth + + + + Apache License, Version 2.0 + https://www.apache.org/licenses/LICENSE-2.0 + + + + + 0.1.0-SNAPSHOT + 25 + 0.12.6 + 1.20.4 + + + + plate-auth-starter + + + + + gitea + https://git.plate-software.de/api/packages/pplate/maven + + + gitea + https://git.plate-software.de/api/packages/pplate/maven + + + + + + + org.codehaus.mojo + flatten-maven-plugin + 1.6.0 + + true + resolveCiFriendliesOnly + + + + flatten + process-resources + flatten + + + flatten.clean + clean + clean + + + + + +