# @bagelink/auth

Modern authentication library for Bagelink applications with built-in SSO support, automatic redirect handling, and Vue components.

## Features

- 🔐 **Complete auth flow** - Login, signup, password reset, email verification
- 🚀 **SSO ready** - Google, GitHub, Microsoft, Apple, Okta, Facebook
- 🔄 **Auto-redirect** - Handles post-login navigation automatically
- 🛡️ **Security built-in** - Protection against open redirect attacks
- 🎨 **Vue components** - Pre-built forms and pages
- 📱 **Type-safe** - Full TypeScript support
- 🧩 **Router integration** - Smart guards for protected routes

## Installation

```bash
npm install @bagelink/auth
```

## Quick Start

### 1. Initialize Auth

```typescript
// main.ts
import { createApp } from 'vue'
import { createAuth } from '@bagelink/auth'
import router from './router'

const auth = createAuth({
  baseURL: 'https://api.your-app.com',
  redirect: {
    loginRoute: 'Login',
    fallback: '/dashboard',
    autoRedirect: true,  // Automatic post-login redirect
  }
})

// Connect router (automatically sets up auth guard)
auth.use(router)

const app = createApp(App)
app.use(router)
app.use(auth)  // Optional: adds $auth globally
app.mount('#app')
```

### 2. Define Routes

```typescript
// router/index.ts
import { createRouter, createWebHistory } from 'vue-router'

const routes = [
  {
    path: '/login',
    name: 'Login',
    component: () => import('@/views/Login.vue'),
  },
  {
    path: '/dashboard',
    name: 'Dashboard',
    component: () => import('@/views/Dashboard.vue'),
    meta: { auth: true },  // Protected route
  },
]

export default createRouter({
  history: createWebHistory(),
  routes,
})
```

### 3. Use in Components

```vue
<script setup lang="ts">
import { useAuth } from '@bagelink/auth'

const auth = useAuth()

async function handleLogin() {
  await auth.login({
    email: 'user@example.com',
    password: 'password'
  })
  // Auto-redirect happens automatically! 🎉
}
</script>

<template>
  <div v-if="auth.user.value">
    <p>Welcome, {{ auth.user.value.name }}!</p>
    <button @click="auth.logout()">Logout</button>
  </div>
</template>
```

## Documentation

- **[REDIRECT_GUIDE.md](./REDIRECT_GUIDE.md)** - Complete redirect configuration guide
- **[COMPLETE_EXAMPLE.md](./COMPLETE_EXAMPLE.md)** - Full working application example
- **[BREAKING_CHANGES.md](./BREAKING_CHANGES.md)** - Migration guide for breaking changes

## Core API

### `createAuth(config)`

Initialize the auth module with redirect configuration:

```typescript
createAuth({
  baseURL: string,              // Required: Your API base URL
  redirect?: {
    loginRoute?: string,        // Login route name (default: 'Login')
    fallback?: string,          // Default redirect after login (default: '/')
    queryKey?: string,          // Query param for redirect URL (default: 'redirect')
    autoRedirect?: boolean,     // Auto-redirect after login (default: true)
    preserveRedirect?: boolean,   // Preserve original URL (default: true)
    noAuthRoutes?: string[],    // Routes requiring no auth (default: ['Login', 'Signup', ...])
    allowedPaths?: RegExp[],    // Optional: Restrict allowed redirects for security
  }
})
```

### `useAuth()`

Returns auth composable with:

```typescript
{
  // State
  user: Ref<User | null>
  accountInfo: Ref<AccountInfo | null>
  
  // Authentication
  login(credentials): Promise<AuthenticationResponse>
  signup(data): Promise<AuthenticationResponse>
  logout(): Promise<void>
  checkAuth(): Promise<boolean>
  
  // SSO
  sso: {
    google: { redirect(), callback() }
    github: { redirect(), callback() }
    microsoft: { redirect(), callback() }
    // ... more providers
  }
  
  // Password Management
  forgotPassword(email): Promise<void>
  resetPassword(token, newPassword): Promise<void>
  changePassword(current, new): Promise<void>
  
  // Profile
  updateProfile(updates): Promise<void>
  
  // Getters
  getFullName(): string
  getIsLoggedIn(): boolean
  getEmail(): string
  getRoles(): string[]
}
```

### Custom Guard Composition

For multi-tenant apps or advanced permission logic, disable the auto-guard and compose your own:

#### Option 1: Using `composeGuards` utility (recommended)

```typescript
import { composeGuards } from '@bagelink/auth'

// Disable auto-guard
auth.use(router, { guard: false })

// Custom org access guard
const orgAccessGuard = () => async (to, from, next) => {
  if (to.meta.requiresOrg) {
    const hasAccess = await checkOrgAccess(to.params.orgId)
    if (!hasAccess) return next('/no-access')
  }
  next()
}

// Compose all guards in sequence
router.beforeEach(composeGuards([
  auth.routerGuard(),  // Auth first
  orgAccessGuard(),    // Then org check
]))
```

#### Option 2: Multiple `beforeEach` calls

```typescript
auth.use(router, { guard: false })

// Guards run in order they're registered
router.beforeEach(auth.routerGuard())

router.beforeEach(async (to, from, next) => {
  // Custom org/tenant check
  if (to.meta.requiresOrg && !await checkOrgAccess(to.params.orgId)) {
    return next('/no-access')
  }
  next()
})
```

#### Option 3: Manual composition

```typescript
auth.use(router, { guard: false })

router.beforeEach(async (to, from, next) => {
  // Run auth guard first
  const authPassed = await new Promise<boolean>((resolve) => {
    auth.routerGuard()(to, from, (result?: any) => {
      if (result !== undefined) {
        next(result)
        resolve(false)
      } else {
        resolve(true)
      }
    })
  })
  
  if (!authPassed) return
  
  // Your custom logic
  if (to.meta.requiresOrg && !await checkOrgAccess(to.params.orgId)) {
    return next('/no-access')
  }
  
  next()
})
```

## Components

### Pre-built Pages

```vue
<script setup>
import { LoginPage, SignupPage, ForgotPasswordPage } from '@bagelink/auth'
</script>

<template>
  <LoginPage
    :texts="{ title: 'Welcome Back' }"
    card-width="400px"
  />
</template>
```

### Form Components

For custom layouts:

```vue
<script setup>
import { LoginForm, SignupForm } from '@bagelink/auth'
</script>

<template>
  <div class="custom-layout">
    <LoginForm
      :texts="{ title: 'Sign In' }"
      :show-google="true"
      :show-github="true"
    />
  </div>
</template>
```

## SSO Integration

### Quick SSO Login

```typescript
const { sso } = useAuth()

// Redirect to Google OAuth
await sso.google.redirect({
  redirect_uri: `${window.location.origin}/auth/callback`
})
```

### SSO Callback Route

```typescript
// router.ts
{
  path: '/auth/callback',
  name: 'AuthCallback',
  component: () => import('@bagelink/auth').then(m => m.Callback),
}
```

### Available Providers

- Google
- GitHub  
- Microsoft
- Apple
- Okta
- Facebook

### SSO Redirect Preservation

When users access a protected route and authenticate via SSO, the original URL is automatically preserved:

```typescript
// User tries to access /dashboard (protected)
// ↓ Redirected to /login?redirect=/dashboard
// ↓ User clicks "Login with Google"
// ↓ Goes to Google OAuth
// ↓ Returns to /auth/callback
// ↓ Automatically redirected to /dashboard ✅
```

**How it works:**
1. The `redirect` query param is stored in `sessionStorage` before SSO redirect
2. After OAuth callback, it's restored to the URL
3. Auto-redirect (or manual fallback) uses it to navigate to the original destination

**Manual SSO with redirect:**
```typescript
// On login page with ?redirect=/dashboard
await sso.google.redirect()
// Redirect param is automatically preserved across the OAuth flow
```

## Advanced Configuration

### Security: Restrict Redirect Paths

```typescript
createAuth({
  baseURL: 'https://api.example.com',
  redirect: {
    allowedPaths: [
      /^\/dashboard/,
      /^\/profile/,
      /^\/settings/,
    ],
    // Only these paths can be redirect targets
  }
})
```

### Disable Auto-Redirect

For custom logic:

```typescript
createAuth({
  redirect: {
    autoRedirect: false,  // Manual control
  }
})

// Then in login handler:
import { performRedirect, getRedirectConfig } from '@bagelink/auth'

await auth.login(credentials)
await performRedirect(router, getRedirectConfig())
```

### Custom Route Meta Key

```typescript
createAuth({
  redirect: {
    authMetaKey: 'requiresAuth',  // Default: 'auth'
  }
})

// Then in routes:
{
  path: '/dashboard',
  meta: { requiresAuth: true }  // Custom meta key
}
```

## Event System

Listen to auth events:

```typescript
import { createAuth, AuthState } from '@bagelink/auth'

const auth = createAuth({ ... })

auth.on(AuthState.LOGIN, () => {
  console.log('User logged in')
})

auth.on(AuthState.LOGOUT, () => {
  console.log('User logged out')
})

auth.on(AuthState.PROFILE_UPDATE, () => {
  console.log('Profile updated')
})
```

Available events:
- `LOGIN`
- `LOGOUT`
- `SIGNUP`
- `PASSWORD_RESET`
- `PASSWORD_CHANGE`
- `PROFILE_UPDATE`
- `AUTH_CHECK`
- `EMAIL_VERIFIED`
- `SESSION_REFRESH`

## TypeScript Support

Full type definitions included:

```typescript
import type {
  User,
  AccountInfo,
  AuthState,
  SSOProvider,
  RedirectConfig,
  LoginTexts,
  SignupTexts,
} from '@bagelink/auth'
```

## Examples

### Protected Route Flow

1. User visits `/dashboard` (protected)
2. Not authenticated → redirected to `/login?redirect=/dashboard`
3. User logs in
4. Auto-redirected to `/dashboard`

### Custom Login Page

```vue
<script setup lang="ts">
import { ref } from 'vue'
import { useAuth } from '@bagelink/auth'

const auth = useAuth()
const email = ref('')
const password = ref('')
const error = ref('')

async function handleLogin() {
  try {
    await auth.login({ email: email.value, password: password.value })
    // Auto-redirect happens automatically!
  } catch (err: any) {
    error.value = err.response?.data?.message || 'Login failed'
  }
}
</script>

<template>
  <form @submit.prevent="handleLogin">
    <input v-model="email" type="email" placeholder="Email" />
    <input v-model="password" type="password" placeholder="Password" />
    <button type="submit">Login</button>
    <p v-if="error" class="error">{{ error }}</p>
  </form>
</template>
```

### Role-Based Access

```typescript
import { useAuth } from '@bagelink/auth'

const auth = useAuth()

// Check if user has admin role
if (auth.getRoles().includes('admin')) {
  // Allow access
}

// Or in route guard
router.beforeEach((to, from, next) => {
  if (to.meta.requiresAdmin && !auth.getRoles().includes('admin')) {
    next('/unauthorized')
  } else {
    next()
  }
})
```

## Migration

See [BREAKING_CHANGES.md](./BREAKING_CHANGES.md) for migration guide from previous versions.

## License

MIT
