diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a6a1179..99c0192 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,21 +1,23 @@ # Each line is a file pattern followed by one or more owners. +# Later matches take precedence over earlier ones. -# These owners will be the default owners for everything in -# the repo. Unless a later match takes precedence, -# @panz3r will be requested for review when someone -# opens a pull request. -* @panz3r +# Default owner for everything +* @panz3r -# Owners of the examples -/examples/* @panz3r -/examples/expo @IronTony +# Core library +/lib/ @panz3r -# Owners of the main library -/lib @panz3r +# Adapter packages (React Native) +/packages/google-signin/ @IronTony -# Owners of the sub-packages -/packages/google-signin @IronTony +# Examples +/examples/expo/ @IronTony + +# CI/CD and repository configuration +/.github/ @panz3r # Make dependency files owner-free package.json +*/package.json +pnpm-workspace.yaml pnpm-lock.yaml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 1a068b8..3c34696 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -7,27 +7,14 @@ body: attributes: value: Thanks for taking the time to fill out this bug report! We'll try to help you as soon as possible so please provide as much information as you can. - - type: textarea - id: what-happened - attributes: - label: What happened? - description: Tell us what you see - validations: - required: true - - - type: textarea - id: what-expected - attributes: - label: What did you expect to happen? - description: Tell us what you wanted to see - validations: - required: true - - - type: textarea - id: reproduction-steps + - type: dropdown + id: package attributes: - label: Steps to reproduce the issue - description: Tell us how we can reproduce this issue. Please be specific and provide sample code if you can + label: Which package is affected? + multiple: true + options: + - "@forward-software/react-auth" + - "@forward-software/react-auth-google" validations: required: true @@ -36,7 +23,7 @@ body: attributes: label: Version description: What version of the library are you using? - placeholder: ex. v1.0.0 + placeholder: ex. v2.0.0 validations: required: true @@ -49,6 +36,7 @@ body: - ReactJS (specify Browser below) - React Native (Android) - React Native (iOS) + - React Native (Web) validations: required: true @@ -64,6 +52,30 @@ body: - Safari - Others + - type: textarea + id: what-happened + attributes: + label: What happened? + description: Tell us what you see + validations: + required: true + + - type: textarea + id: what-expected + attributes: + label: What did you expect to happen? + description: Tell us what you wanted to see + validations: + required: true + + - type: textarea + id: reproduction-steps + attributes: + label: Steps to reproduce the issue + description: Tell us how we can reproduce this issue. Please be specific and provide sample code if you can + validations: + required: true + - type: textarea id: logs attributes: diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index c418f0f..c4925b6 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -7,6 +7,17 @@ body: attributes: value: Thanks for taking the time to fill out this feature request! We'll try to help you as soon as possible so please provide as much information as you can. + - type: dropdown + id: package + attributes: + label: Which package is this for? + multiple: true + options: + - "@forward-software/react-auth" + - "@forward-software/react-auth-google" + validations: + required: true + - type: textarea id: related-issue attributes: @@ -37,3 +48,12 @@ body: label: Additional context description: | Add any other context or screenshots about the feature request here. + + - type: checkboxes + id: terms + attributes: + label: Code of Conduct + description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/forwardsoftware/react-auth/blob/main/CODE_OF_CONDUCT.md) + options: + - label: I agree to follow this project's Code of Conduct + required: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml index de4ec4f..2969df5 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -20,6 +20,7 @@ updates: directories: - "/" - "/lib" + - "/packages/google-signin" exclude-paths: - "examples/*" schedule: diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index a2df2de..c013c46 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,3 +1,12 @@ +## Affected Package(s) + + + +- [ ] `@forward-software/react-auth` (lib) +- [ ] `@forward-software/react-auth-google` (packages/google-signin) +- [ ] Examples +- [ ] CI/CD / Repository configuration + ## Related Issue(s) + + +None + ## How to Test 1. **CI Checks:** Verify that all automated tests (`Vitest`) and build steps pass successfully on this PR. 2. **Local Verification (Optional):** - * Run `pnpm install` (or equivalent). - * Run the development server (`pnpm dev` or equivalent) for the library or examples to ensure Vite starts correctly. - * Run a build (`pnpm build` or equivalent) to ensure it completes successfully. + * Run `pnpm install` to install dependencies. + * Run `pnpm --filter test` to run tests for the affected package. + * Run `pnpm --filter build` to verify the build succeeds. + * Run `pnpm --filter lint` to check for linting errors. ## Checklist @@ -39,6 +56,7 @@ Link the relevant issue(s) here, if any. E.g., * [ ] I have reviewed my own code and lock file changes * [ ] I have checked for any potential security implications * [ ] I have verified the changes work as expected +* [ ] My commit messages follow [Conventional Commits](https://www.conventionalcommits.org/) format ## Notes for Reviewers diff --git a/.gitignore b/.gitignore index 5a45ec9..84bd79e 100644 --- a/.gitignore +++ b/.gitignore @@ -334,3 +334,6 @@ $RECYCLE.BIN/ # End of https://www.toptal.com/developers/gitignore/api/macos,windows,intellij,visualstudiocode,node +# Copilot instructions (local overrides) +.github/copilot-instructions.md + diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..c33dbac --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,465 @@ +# AGENTS.md + +> Instructions for AI coding agents working on the `react-auth` monorepo. + +--- + +## Project overview + +This is a monorepo for **React Auth**, a library that simplifies authentication flows in React and React Native apps. It is managed with [pnpm workspaces](https://pnpm.io/workspaces) and contains two publishable packages plus example apps. + +### Packages + +| Package | Path | npm name | Description | +| --- | --- | --- | --- | +| Core library | `lib/` | `@forward-software/react-auth` | Framework-agnostic auth primitives: `AuthClient` interface, `createAuth()`, `AuthProvider`, `useAuthClient` hook, `EnhancedAuthClient` wrapper with event emitter and state management | +| Google Sign-In adapter | `packages/google-signin/` | `@forward-software/react-auth-google` | Ready-made `AuthClient` implementation and `GoogleSignInButton` for Web (Google Identity Services) and React Native (Expo native module) | + +### Examples + +Located in `examples/`. These are **not** published — they exist for documentation and manual testing only. + +- `examples/base/` — minimal Vite + React example +- `examples/reqres/` — authenticates against the ReqRes API +- `examples/refresh-token/` — demonstrates token refresh with Axios interceptors +- `examples/expo/` — React Native (Expo) integration + +--- + +## Setup commands + +```sh +# Install all dependencies (use frozen lockfile for CI-like behavior) +pnpm install + +# Build all packages +pnpm -r build + +# Run all tests +pnpm -r test + +# Lint all packages +pnpm -r lint + +# Clean build outputs +pnpm -r clean +``` + +### Per-package commands + +```sh +# Build a specific package +pnpm --filter @forward-software/react-auth build +pnpm --filter @forward-software/react-auth-google build + +# Test a specific package +pnpm --filter @forward-software/react-auth test +pnpm --filter @forward-software/react-auth-google test + +# Watch mode for tests (useful during development) +pnpm --filter @forward-software/react-auth test:watch +``` + +The `pnpm-workspace.yaml` defines workspace members as `lib` and `packages/*`. The `catalog:` protocol in `pnpm-workspace.yaml` pins shared dependency versions (React, TypeScript, Vite, Vitest, etc.) across all packages. + +--- + +## Architecture + +### Core library (`lib/`) + +The core library exposes two things from `lib/src/index.ts`: +- `createAuth` function +- `AuthClient` type + +#### Key source files + +- **`lib/src/auth.tsx`** — Contains all core logic: + - `AuthClient` interface — the contract adapters must implement. Only `onLogin()` is required; all other lifecycle hooks (`onInit`, `onPostInit`, `onPreLogin`, `onPostLogin`, `onPreRefresh`, `onRefresh`, `onPostRefresh`, `onPreLogout`, `onLogout`, `onPostLogout`) are optional. + - `AuthClientEnhancements` class — wraps an `AuthClient` with state management (`isInitialized`, `isAuthenticated`, `tokens`), event emission (`on`/`off`/`emit` for init/login/refresh/logout events), `useSyncExternalStore` integration (`subscribe`/`getSnapshot`), and a refresh queue that deduplicates concurrent refresh calls. + - `wrapAuthClient()` — uses `Object.setPrototypeOf` to merge the enhancement class with the original `AuthClient` instance, producing an `EnhancedAuthClient`. + - `createAuth()` — creates a React context, wraps the provided `AuthClient`, and returns `{ AuthProvider, authClient, useAuthClient }`. + - `AuthProvider` — React component that calls `authClient.init()` on mount, shows optional `LoadingComponent`/`ErrorComponent`, and provides the auth context to children. + - `useAuthClient` — hook that reads from the auth context (throws if used outside `AuthProvider`). + +- **`lib/src/utils.ts`** — Contains: + - `Deferred` — Promise wrapper used for the refresh queue. + - `createEventEmitter()` — simple typed event emitter (on/off/emit). + +#### Important patterns + +- The library targets **ES6** and uses **`react-jsx`** JSX transform. +- `use-sync-external-store/shim` is the only runtime dependency (peer dependency is `react >= 16.8`). +- TypeScript strict mode is enabled. +- The `EnhancedAuthClient` type is `AC & AuthClientEnhancements` — it preserves the original client's type while adding enhanced properties/methods. + +### Google Sign-In adapter (`packages/google-signin/`) + +This package provides a `GoogleAuthClient` class (implements `AuthClient`) and a `GoogleSignInButton` component, with platform-specific implementations resolved at build/bundle time. + +#### Entry points + +- `src/index.ts` — Web entry: re-exports from `src/web/` +- `src/index.native.ts` — React Native entry: re-exports from `src/native/` +- Both export: `GoogleAuthClient`, `GoogleSignInButton`, and all types from `src/types.ts` + +#### Key source files + +- **`src/types.ts`** — Shared types: `GoogleAuthTokens`, `GoogleAuthCredentials`, `TokenStorage` interface, `GoogleAuthConfig`, `GoogleWebAuthConfig`, `GoogleNativeAuthConfig`. +- **`src/web/GoogleAuthClient.ts`** — Web implementation using Google Identity Services (GSI). Uses `localStorage` by default for token persistence. Parses JWT `exp` claim to track expiration. +- **`src/native/GoogleAuthClient.ts`** — React Native implementation using Expo native modules. Requires external `storage` (e.g., MMKV). Supports silent sign-in for token refresh. +- **`src/web/GoogleSignInButton.tsx`** — Renders Google's official GSI button on web. +- **`src/native/GoogleSignInButton.tsx`** — Native sign-in button component. +- **`src/native/GoogleSignInModule.ts`** — Expo module bridge (calls into native Swift/Kotlin code). +- **`src/web/gsi.ts`** — Low-level GSI script loading and initialization utilities, exposed as a separate export (`@forward-software/react-auth-google/web/gsi`). + +#### Platform resolution + +The `package.json` uses the `"react-native"` field and conditional `"exports"` to let bundlers resolve the correct entry point: +```json +{ + "main": "dist/index.js", + "react-native": "dist/index.native.js", + "exports": { + ".": { + "react-native": "./dist/index.native.js", + "default": "./dist/index.js" + } + } +} +``` + +#### Native modules + +- **iOS**: `ios/GoogleSignInModule.swift` — Swift implementation using Apple's Authentication Services. +- **Android**: `android/src/main/java/expo/modules/googlesignin/GoogleSignInModule.kt` — Kotlin implementation using Android Credential Manager. +- Configured via `expo-module.config.json` for Expo autolinking. + +--- + +## Testing + +### Framework + +All packages use **Vitest** with **jsdom** environment, **@testing-library/react**, and **@testing-library/jest-dom**. + +Vitest config (identical in both packages): +```ts +{ + environment: "jsdom", + globals: true, + include: ["**/*.{test,spec}.{js,jsx,ts,tsx}"], +} +``` + +### Test conventions + +- Test files live in a `test/` directory alongside `src/`. +- File naming: `*.spec.ts` or `*.spec.tsx`. +- Tests use the **Arrange / Act / Assert** pattern (with explicit comments). +- Mock auth clients are defined in `test/test-utils.tsx` (core lib) and `test/test-utils.ts` (google-signin). +- Use `vi.spyOn()` for mocking, `vi.fn()` for stubs. +- React components are tested with `@testing-library/react` (`render`, `act`, `cleanup`). +- Always call `rtl.cleanup` in `afterEach`. + +### Core lib test structure + +- `test/authClient.spec.ts` — Unit tests for `EnhancedAuthClient` (init, login, refresh, logout lifecycle events and hooks). +- `test/context.spec.ts` — Tests for the React context (`useAuthClient` hook behavior). +- `test/provider.spec.tsx` — Tests for `AuthProvider` (initialization, loading/error components, auth state propagation). +- `test/test-utils.tsx` — `MockAuthClient` class, `createMockAuthClient()`, `createMockAuthClientWithHooks()`, `createChild()` helper, `flushPromises()`. + +### Google Sign-In test structure + +- `test/GoogleAuthClient.web.spec.ts` — Web adapter tests (token persistence, login, logout, expiration handling). +- `test/GoogleAuthClient.native.spec.ts` — Native adapter tests. +- `test/test-utils.ts` — `MockTokenStorage` class, `createMockIdToken()`, `createExpiredMockIdToken()`. + +### Running tests + +```sh +# Run all tests +pnpm -r test + +# Run tests for a specific package +pnpm --filter @forward-software/react-auth test +pnpm --filter @forward-software/react-auth-google test + +# Run a specific test file +cd lib && pnpm vitest run test/authClient.spec.ts +cd packages/google-signin && pnpm vitest run test/GoogleAuthClient.web.spec.ts + +# Run a specific test by name +cd lib && pnpm vitest run -t "should notify success" +``` + +--- + +## Code style + +- **TypeScript** strict mode in all packages. +- **Target**: ES6. +- **JSX transform**: `react-jsx` (no `import React` needed in JSX files, but the core lib does import React explicitly). +- **Module resolution**: `node`. +- Linting via **ESLint**: `pnpm --filter lint`. +- No Prettier config at root — follow existing formatting conventions in each file. +- Use single quotes for strings (following existing code style). +- Export types with `export type` when exporting only type information. + +### Import ordering + +Follow this order (separated by blank lines where shown in existing code): + +1. **External dependencies** — React, third-party libraries (e.g., `react`, `expo-modules-core`, `use-sync-external-store`) +2. **Type-only imports from external deps** — using `import type { ... }` (e.g., `import type { PropsWithChildren } from 'react'`) +3. **Internal value imports** — from `../types`, `./utils`, `./gsi`, etc. +4. **Internal type-only imports** — using `import type { ... }` from local files + +```ts +// ✅ Correct +import React, { useEffect, useRef, useCallback } from 'react'; +import type { GoogleAuthCredentials, GoogleWebAuthConfig } from '../types'; +import { loadGsiScript, initializeGsi, renderGsiButton } from './gsi'; +import type { GsiButtonConfig } from './gsi'; +``` + +Always use `import type` for imports that are only used as types — never import a type with a regular `import` if it's not used as a value. + +### Type definitions + +- Use `type` for object shapes, unions, and intersections: `export type MyTokens = { ... }` +- Use `interface` only for contracts that classes implement: `export interface TokenStorage { ... }` +- Prefer `type` over `interface` when not implementing with a class +- Export types directly from the file where they are defined — re-export from `index.ts` using `export * from './types'` +- Place shared types in a dedicated `types.ts` file per package + +### Class conventions + +- `AuthClient` implementations should be classes (not plain objects) for adapter packages +- Private fields use the `private` keyword (not `#` private fields) +- Constructor should apply defaults using spread: `this.config = { scopes: DEFAULT_SCOPES, ...config }` +- Config, storage, and storageKey are `private` readonly fields set in the constructor + +### Error handling + +- Use bare `catch {}` (without binding the error) when the error is intentionally ignored (e.g., best-effort cleanup like GSI revoke) +- Use `catch (err)` when the error needs to be forwarded (e.g., to `onError` callbacks) +- Throw `new Error('descriptive message')` — never throw raw strings or objects +- Error messages should describe what went wrong and what the caller should do, without including sensitive data + +### Naming conventions + +- **Files**: PascalCase for classes/components (`GoogleAuthClient.ts`, `GoogleSignInButton.tsx`), camelCase for utilities (`gsi.ts`, `utils.ts`), kebab-case for test utils (`test-utils.ts`) +- **Types**: PascalCase with descriptive suffixes — `GoogleAuthTokens`, `GoogleAuthCredentials`, `GoogleWebAuthConfig`, `TokenStorage` +- **Constants**: UPPER_SNAKE_CASE — `DEFAULT_SCOPES`, `DEFAULT_STORAGE_KEY` +- **Test files**: `{Subject}.spec.ts` or `{Subject}.{platform}.spec.ts` (e.g., `GoogleAuthClient.web.spec.ts`) +- **Platform-specific files**: `index.ts` (web default), `index.native.ts` (React Native) + +--- + +## Writing tests + +### Test file template + +```ts +import { describe, it, expect, vi, afterEach } from 'vitest'; +import * as rtl from '@testing-library/react'; +import '@testing-library/jest-dom'; + +// Import from src +import { createAuth } from '../src'; + +// Import test utilities +import { createMockAuthClient } from './test-utils'; + +afterEach(rtl.cleanup); + +describe('FeatureName', () => { + describe('scenario', () => { + it('should do something specific', async () => { + // Arrange + const mock = createMockAuthClient(); + vi.spyOn(mock, 'onInit').mockResolvedValue(null); + + // Act + await rtl.act(async () => { + // ... trigger the action + }); + + // Assert + expect(mock.onInit).toHaveBeenCalledTimes(1); + }); + }); +}); +``` + +### Test rules + +- **Always** use explicit `// Arrange`, `// Act`, `// Assert` comments +- **Always** call `afterEach(rtl.cleanup)` at the top level of the test file +- **Always** wrap async React operations in `rtl.act(async () => { ... })` +- **Never** test implementation details — test behavior (events emitted, state changes, rendered output) +- **Never** import from `dist/` — always import from `../src` or `../src/auth` +- **Mock only what you own** — mock `AuthClient` methods, not React internals or library code +- Use `vi.spyOn(object, 'method')` to spy on existing methods; use `vi.fn()` for standalone stubs +- Use `mockResolvedValue` / `mockResolvedValueOnce` for async mocks, `mockReturnValue` for sync +- Test both success and failure paths for each lifecycle method (init, login, refresh, logout) +- Test that lifecycle hooks (onPreLogin, onPostLogin, etc.) are called in the correct order +- Test event emissions (e.g., `loginStarted`, `loginSuccess`, `loginFailed`) via `.on()` subscriptions + +### Writing adapter tests + +For adapter package tests, follow these additional patterns: + +- Create a `MockTokenStorage` class implementing `TokenStorage` with a `Map`-based in-memory store and a `clear()` method for test cleanup +- Create helper functions to generate mock tokens (e.g., `createMockIdToken(claims)`, `createExpiredMockIdToken()`) +- Test token persistence: verify tokens are stored after login and cleared after logout +- Test token restoration: verify `onInit()` restores valid tokens and rejects expired ones +- Test with and without `persistTokens` option +- Separate web and native tests into different files: `*.web.spec.ts` and `*.native.spec.ts` + +--- + +## How to contribute a fix to the core lib + +1. Read and understand the relevant source in `lib/src/auth.tsx` and `lib/src/utils.ts`. +2. Write or update tests in `lib/test/` following existing patterns (Arrange/Act/Assert, use `createMockAuthClient`). +3. Run `pnpm --filter @forward-software/react-auth test` and ensure all tests pass. +4. Run `pnpm --filter @forward-software/react-auth build` to verify the build succeeds. +5. Run `pnpm --filter @forward-software/react-auth lint` to check for lint errors. + +## How to implement or enhance an adapter package + +Adapter packages live under `packages/` and must: + +1. **Implement the `AuthClient` interface** from `@forward-software/react-auth`. At minimum, implement `onLogin()`. Optionally implement `onInit`, `onLogout`, `onRefresh`, and lifecycle hooks. +2. **Support platform-specific entry points** if targeting both web and React Native: + - `src/index.ts` — web entry, re-exports from `src/web/` + - `src/index.native.ts` — React Native entry, re-exports from `src/native/` + - Configure `"main"`, `"react-native"`, and `"exports"` in `package.json` +3. **Define shared types** in a `src/types.ts` file (tokens, credentials, config, storage interface). +4. **Provide a UI component** (e.g., `SignInButton`) for both platforms if applicable. +5. **Add `@forward-software/react-auth`** as both a `devDependency` and a `peerDependency`. +6. **Write tests** in a `test/` directory with platform-specific spec files (e.g., `*.web.spec.ts`, `*.native.spec.ts`). Create mock utilities in `test/test-utils.ts`. +7. **Use the same build tooling**: TypeScript compilation with `tsc`, Vitest for testing, same `tsconfig.json` structure. +8. **Register the package in CI/CD and release configuration** (critical — the package will not be tested or published otherwise): + - `pnpm-workspace.yaml` — already covered by the `packages/*` glob, no action needed unless the package is outside `packages/`. + - `.github/workflows/build-test.yml` — add the new package's npm name to **both** the `test` and `build` job `matrix.package` arrays so it is tested and built in CI. + - `release-please-config.json` — add an entry under `"packages"` with the package path (e.g., `"packages/my-adapter": {}`) to enable automated versioning, changelog generation, and npm publishing via the release workflow. + - `.github/dependabot.yml` — add the package path to the `directories` list under the `npm` package ecosystem so its dependencies are monitored for updates. + - `.github/ISSUE_TEMPLATE/bug_report.yml` — add the new package name to the "Which package is affected?" dropdown options. + - `.github/ISSUE_TEMPLATE/feature_request.yml` — add the new package name to the "Which package is this for?" dropdown options. + - `.github/CODEOWNERS` — add a rule for the new package path with the appropriate owner(s). +9. **Update documentation**: + - `README.md` — add the new package to the **Packages** table (with npm badge and description). + - `SECURITY.md` — add the new package and its supported version to the **Supported Versions** table. + - Create a `README.md` in the package directory with install instructions, quick start, API reference, and consistent badges/footer (follow the structure of `packages/google-signin/README.md`). + +### Adapter package script conventions + +```json +{ + "scripts": { + "build:code": "tsc --removeComments", + "build:types": "tsc --declaration --emitDeclarationOnly", + "build": "npm-run-all clean build:*", + "lint": "eslint src", + "test": "vitest", + "test:watch": "vitest watch", + "clean": "rimraf dist" + } +} +``` + +--- + +## CI/CD + +> **Important**: When adding a new package, you **must** update the GitHub Actions workflows and release configuration. Without this, the package will not be tested, built, or published. See the checklist in "How to implement or enhance an adapter package" step 8 above. + +### Build & Test (`.github/workflows/build-test.yml`) + +- Runs on pushes to all branches except `main`. +- Tests each package against Node.js matrix: `lts/-1`, `lts/*`, `latest`. +- Builds each package separately. +- Uses `pnpm i --frozen-lockfile` then `pnpm install --no-frozen-lockfile --config.auto-install-peers=true` for peer dependencies. + +### Release (`.github/workflows/release.yml`) + +- Runs on pushes to `main` and weekly on Tuesday evenings. +- Uses [Release Please](https://github.com/googleapis/release-please-action) to automate versioning and changelogs. +- Builds and publishes to npm with provenance (`id-token: write`). +- Configuration in `release-please-config.json`. + +--- + +## PR guidelines + +- Run `pnpm --filter lint` and `pnpm --filter test` before committing. +- Ensure `pnpm --filter build` succeeds. +- Add or update tests for any code changes. +- Follow [Conventional Commits](https://www.conventionalcommits.org/) for commit messages (used by Release Please for changelog generation). +- See [CONTRIBUTING.md](CONTRIBUTING.md) for full contribution guidelines. + +### Commit message format + +Release Please uses commit messages to determine version bumps and generate changelogs. Use these prefixes: + +``` +feat: add token expiration event # → minor version bump (0.x.0) +fix: prevent duplicate refresh calls # → patch version bump (0.0.x) +fix!: change onRefresh signature # → major version bump (x.0.0) — breaking change +chore: update dev dependencies # → no release +docs: update README examples # → no release +test: add missing logout tests # → no release +refactor: extract token validation logic # → no release +``` + +For scoped changes, include the package scope: + +``` +feat(google-signin): add One Tap support +fix(react-auth): handle concurrent refresh race condition +``` + +### Checklist before submitting + +1. ✅ Code compiles: `pnpm --filter build` +2. ✅ Linting passes: `pnpm --filter lint` +3. ✅ All tests pass: `pnpm --filter test` +4. ✅ New tests added for new/changed code +5. ✅ No `console.log` or debug statements left in source code +6. ✅ No tokens, credentials, or secrets in error messages +7. ✅ Commit message follows Conventional Commits format +8. ✅ If adding a new package: CI workflows and release config updated + +--- + +## Common pitfalls + +Avoid these mistakes that agents frequently make: + +- **Do not modify `package.json` version fields** — versions are managed automatically by Release Please. Never manually bump `"version"`. +- **Do not add `node_modules` or `dist` to commits** — these are in `.gitignore`. +- **Do not break the `AuthClient` interface** — adding optional methods is fine; changing the signature of `onLogin` or removing methods is a breaking change requiring a `feat!:` or `fix!:` commit. +- **Do not add React as a dependency** — it must remain a `peerDependency`. The same applies to `expo-modules-core` and `react-native` in adapter packages. +- **Do not use `any` in TypeScript** — use proper types or generics. The codebase uses strict mode. +- **Do not introduce new runtime dependencies** unless absolutely necessary — the core lib has only one dependency (`use-sync-external-store`). Prefer zero-dependency implementations. +- **Do not mix platform code** — web code goes in `src/web/`, native code goes in `src/native/`. Shared types go in `src/types.ts`. Never import from `react-native` in web files or from browser APIs in native files. +- **Do not skip the build step** — `pnpm build` must succeed because the published package uses `dist/`, not `src/`. +- **Do not use relative imports crossing package boundaries** — always use the npm package name (e.g., `import { createAuth } from '@forward-software/react-auth'`, not `import { createAuth } from '../../lib/src'`). + +--- + +## Security considerations + +This project handles authentication tokens and credentials. Follow these rules when making changes: + +- **Never log or expose tokens** — do not add `console.log`, debug logging, or error messages that include token values, credentials, or secrets. +- **JWT parsing is read-only** — the `base64UrlDecode` / `exp` extraction in `GoogleAuthClient` is used only to check expiration. Never modify JWT contents or attempt to forge tokens. +- **Token storage** — tokens may be persisted via the `TokenStorage` interface (localStorage on web, MMKV or AsyncStorage on React Native). Never store tokens in cookies, URL parameters, or global variables. +- **Validate at boundaries** — when processing external input (credentials from sign-in flows, tokens from storage), validate the shape before using it (e.g., check `idToken` exists before accessing it). +- **No credential leakage in errors** — error messages thrown by adapters must not include user credentials or token values. Use generic messages like `"credentials with idToken are required"`. +- **HTTPS only** — any examples or documentation referencing API endpoints should use `https://` URLs. +- **Nonce support** — the Google adapter supports a `nonce` parameter to bind ID tokens to a session and prevent replay attacks. Preserve this feature when modifying the sign-in flow. +- **Peer dependency ranges** — when updating dependency versions, check for known security vulnerabilities. Do not pin to versions with known CVEs. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 39503bd..42dbcc3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -58,7 +58,7 @@ We actively welcome your pull requests: #### Use a consistent Coding Style This project uses [ESLint](https://eslint.org/) and [Prettier](https://prettier.io/) to maintain a unified coding style. -Before committing your changes remember to run `yarn lint` and check possible warnings and errors. +Before committing your changes remember to run `pnpm -r lint` and check possible warnings and errors. #### Any contributions you make will be under the MIT Software License diff --git a/README.md b/README.md index 683dd4f..74bd8d8 100644 --- a/README.md +++ b/README.md @@ -4,17 +4,148 @@ [![license](https://img.shields.io/github/license/forwardsoftware/react-auth.svg)](https://github.com/forwardsoftware/react-auth/blob/main/LICENSE) [![Build & Test](https://github.com/forwardsoftware/react-auth/actions/workflows/build-test.yml/badge.svg)](https://github.com/forwardsoftware/react-auth/actions/workflows/build-test.yml) [![Github Issues](https://img.shields.io/github/issues/forwardsoftware/react-auth.svg)](https://github.com/forwardsoftware/react-auth/issues) +## Packages + +This monorepo contains the following packages: + +| Package | Version | Description | +| --- | --- | --- | +| [`@forward-software/react-auth`](./lib) | [![npm](https://img.shields.io/npm/v/@forward-software/react-auth)](https://npmjs.com/package/@forward-software/react-auth) | Core library — provides `AuthClient`, `AuthProvider`, `createAuth`, and `useAuthClient` for integrating authentication flows in any React or React Native app | +| [`@forward-software/react-auth-google`](./packages/google-signin) | [![npm](https://img.shields.io/npm/v/@forward-software/react-auth-google)](https://npmjs.com/package/@forward-software/react-auth-google) | Google Sign-In adapter — ready-made `AuthClient` implementation and drop-in `GoogleSignInButton` for Web and React Native (Expo) | ## Examples -The `examples` folder contains some examples of how you can integrate this library in your React app. +The `examples` folder contains some examples of how you can integrate these libraries in your React app: + +| Example | Description | +| --- | --- | +| [`base`](./examples/base) | Basic authentication flow using `@forward-software/react-auth` | +| [`reqres`](./examples/reqres) | Authentication using the [ReqRes](https://reqres.in) API | +| [`refresh-token`](./examples/refresh-token) | Token refresh flow with Axios interceptors | +| [`expo`](./examples/expo) | React Native (Expo) integration | + +## Quick Start + +Install the core library: + +```sh +npm install @forward-software/react-auth +``` + +Define your auth client: + +```ts +import { createAuth, type AuthClient } from '@forward-software/react-auth'; + +const authClient: AuthClient = { + onLogin: async (credentials) => { + // Exchange credentials for tokens + return { token: '...' }; + }, +}; + +export const { AuthProvider, useAuthClient } = createAuth(authClient); +``` + +Wrap your app: + +```tsx +import { AuthProvider } from './auth'; + +function App() { + return ( + + + + ); +} +``` + +Use the hook to interact with the auth client: + +```tsx +import { useAuthClient } from './auth'; + +function LoginButton() { + const authClient = useAuthClient(); + return ; +} +``` + +For more details, see the [`@forward-software/react-auth` README](./lib/README.md). + +> **Looking for a ready-made integration?** The [`@forward-software/react-auth-google`](./packages/google-signin) package provides a drop-in Google Sign-In adapter with a pre-built `AuthClient` and `GoogleSignInButton` for both Web and React Native. See its [README](./packages/google-signin/README.md) for setup instructions. +## Project Structure + +This project is a monorepo managed with [pnpm workspaces](https://pnpm.io/workspaces): + +``` +react-auth/ +├── lib/ # @forward-software/react-auth (core library) +│ ├── src/ # Source code +│ └── test/ # Unit tests +├── packages/ +│ └── google-signin/ # @forward-software/react-auth-google (Google Sign-In adapter) +│ ├── src/ +│ │ ├── web/ # Web implementation (Google Identity Services) +│ │ └── native/ # React Native implementation (Expo module) +│ ├── android/ # Android native module +│ ├── ios/ # iOS native module +│ └── test/ # Unit tests +└── examples/ # Example applications + ├── base/ # Basic React example + ├── reqres/ # ReqRes API example + ├── refresh-token/ # Token refresh example + └── expo/ # React Native (Expo) example +``` + +## Getting Started + +### Prerequisites + +- [Node.js](https://nodejs.org/) (LTS recommended) +- [pnpm](https://pnpm.io/) (see `packageManager` in `package.json` for the required version) + +### Installation + +```sh +pnpm install +``` + +### Building + +```sh +# Build all packages +pnpm -r build +``` + +### Testing + +```sh +# Run tests across all packages +pnpm -r test +``` + +## Contributing + +Contributions are welcome! Here's how to get started: + +1. Fork the repo and create your branch from `main` +2. Install dependencies: `pnpm install` +3. Make your changes and add/update tests +4. Ensure everything works: `pnpm -r lint && pnpm -r test && pnpm -r build` +5. Commit using [Conventional Commits](https://www.conventionalcommits.org/) (e.g., `feat: ...`, `fix: ...`) +6. Open a pull request + +For more details, read the [Contributing Guide](CONTRIBUTING.md) and the [Code of Conduct](CODE_OF_CONDUCT.md). + +Found a security issue? Please report it privately — see our [Security Policy](SECURITY.md). ## Credits This library has been inspired by [`react-keycloak`](https://github.com/react-keycloak/react-keycloak) and similar libraries. - ## License MIT diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..9361019 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,49 @@ +# Security Policy + +## Reporting a Vulnerability + +If you discover a security vulnerability in this project, please report it responsibly. **Do not open a public GitHub issue.** + +Instead, please use one of the following methods: + +### GitHub Private Vulnerability Reporting + +Use [GitHub's private vulnerability reporting](https://github.com/forwardsoftware/react-auth/security/advisories/new) to submit a report directly through the repository. This is the preferred method. + +### Email + +Send an email to the maintainers at [security@forwardsoftware.solutions](mailto:security@forwardsoftware.solutions) with: + +- A description of the vulnerability +- Steps to reproduce the issue +- The potential impact +- Any suggested fix (if available) + +## Response Timeline + +- **Acknowledgment**: We will acknowledge receipt of your report within **48 hours**. +- **Assessment**: We will assess the vulnerability and provide an initial response within **5 business days**. +- **Fix**: Critical vulnerabilities will be prioritized and patched as soon as possible. + +## Supported Versions + +Security updates are provided for the latest major version of each package: + +| Package | Supported Version | +| --- | --- | +| `@forward-software/react-auth` | `2.x` | +| `@forward-software/react-auth-google` | `1.x` | + +## Scope + +The following areas are in scope for security reports: + +- Token handling and storage +- Authentication flow vulnerabilities +- Credential leakage +- Cross-site scripting (XSS) in rendered components +- Dependency vulnerabilities + +## Acknowledgments + +We appreciate the efforts of security researchers and will credit reporters (with their permission) in the release notes when a vulnerability is fixed. diff --git a/examples/expo/README.md b/examples/expo/README.md index 21c667d..87d0e3d 100644 --- a/examples/expo/README.md +++ b/examples/expo/README.md @@ -1,6 +1,6 @@ # React Auth - Expo Example -This is an example implementation of [react-auth](https://github.com/webbable/react-auth) using Expo and React Native. +This is an example implementation of [react-auth](https://github.com/forwardsoftware/react-auth) using Expo and React Native. ## Prerequisites diff --git a/lib/README.md b/lib/README.md index d83105b..096882e 100644 --- a/lib/README.md +++ b/lib/README.md @@ -147,31 +147,13 @@ The `createAuth` function wraps your `AuthClient` implementation with an `Enhanc - `subscribe(() => { })`, subscribe to AuthClient state changes - `getSnapshot()`, returns the current state of the AuthClient -### React components - -Setup React components to interact with the AuthClient using the `createAuth` function exported by this library - -```ts -import { createAuth } from '@forward-software/react-auth'; - -export const { AuthProvider, useAuthClient } = createAuth(authClient); -``` - -the `createAuth` function returns: - -- `AuthProvider`, the context Provider component that should wrap your app and provide access to your AuthClient -- `useAuthClient`, the hook to retrieve and interact with your AuthClient - -#### AuthProvider - -The context Provider component that should wrap your app and provide access to your AuthClient, this component also accepts 2 additional props +## Examples -- `ErrorComponent`, displayed when the AuthClient initialization fails -- `LoadingComponent`, displayed while the AuthClient is being initialized +The [`examples`](https://github.com/forwardsoftware/react-auth/tree/main/examples) folder in the repository contains some examples of how you can integrate this library in your React app. -## Examples +## Contributing -The `examples` folder contains some examples of how you can integrate this library in your React app. +Contributions are welcome! Please read the [Contributing Guide](https://github.com/forwardsoftware/react-auth/blob/main/CONTRIBUTING.md) and the [Code of Conduct](https://github.com/forwardsoftware/react-auth/blob/main/CODE_OF_CONDUCT.md) before submitting a pull request. ## Credits diff --git a/packages/google-signin/README.md b/packages/google-signin/README.md index 7a45aa8..bf78d03 100644 --- a/packages/google-signin/README.md +++ b/packages/google-signin/README.md @@ -2,6 +2,10 @@ > Google Sign-In adapter for [@forward-software/react-auth](https://github.com/forwardsoftware/react-auth) - Web and React Native +[![license](https://img.shields.io/github/license/forwardsoftware/react-auth.svg)](https://github.com/forwardsoftware/react-auth/blob/main/LICENSE) [![Build & Test](https://github.com/forwardsoftware/react-auth/actions/workflows/build-test.yml/badge.svg)](https://github.com/forwardsoftware/react-auth/actions/workflows/build-test.yml) [![Github Issues](https://img.shields.io/github/issues/forwardsoftware/react-auth.svg)](https://github.com/forwardsoftware/react-auth/issues) + +[![npm](https://img.shields.io/npm/v/@forward-software/react-auth-google)](https://npmjs.com/package/@forward-software/react-auth-google) [![NPM downloads](https://img.shields.io/npm/dm/@forward-software/react-auth-google.svg)](https://npmjs.com/package/@forward-software/react-auth-google) + Self-contained Google Sign-In integration with no external auth wrapper dependencies. Provides a ready-made `AuthClient` implementation and a drop-in `GoogleSignInButton` for both platforms. --- @@ -349,6 +353,10 @@ Available via `import { GoogleSignInModule } from '@forward-software/react-auth- --- +## Contributing + +Contributions are welcome! Please read the [Contributing Guide](https://github.com/forwardsoftware/react-auth/blob/main/CONTRIBUTING.md) and the [Code of Conduct](https://github.com/forwardsoftware/react-auth/blob/main/CODE_OF_CONDUCT.md) before submitting a pull request. + ## License MIT @@ -356,3 +364,5 @@ MIT --- Made with ✨ & ❤️ by [ForWarD Software](https://github.com/forwardsoftware) and [contributors](https://github.com/forwardsoftware/react-auth/graphs/contributors) + +If you found this project to be helpful, please consider contacting us to develop your React and React Native projects.