# 사용자 인증 및 권한

## Content

이 가이드는 `js-collaboration`에서 **사용자 인증**과 **메시지 권한 검증**을 구현하는 방법을 설명합니다. **미들웨어**를 사용하여 사용자 신원을 검증하고, 메시지를 수신할 때 권한을 확인하여 작업 허용 여부를 판단합니다.

## **요약**

`js-collaboration`은 토큰 검증을 통해 안전한 인증을 수행하고, 메시지 수준의 권한 제어를 구현합니다. 이를 통해 다음을 보장합니다.

* 인증된 클라이언트만 서버 연결을 설정할 수 있음
* 정책을 통해 메시지 처리 권한이 강제됨

**핵심 포인트:**

* **인증(Authentication)**: 토큰을 통해 사용자 신원을 검증하여 클라이언트가 합법적인 사용자인지 확인
* **권한 검증(Permission Validation)**: 사용자 정보를 저장하고 조회하여 메시지 권한 제어 수행

> type=warning
> 이 예제의 토큰 검증 및 권한 검증은 데모용입니다. 실제 애플리케이션에서는 비즈니스 요구사항에 따라 더 안전하고 복잡한 검증 메커니즘을 구현해야 합니다.

## **워크플로우**

**클라이언트**

1. 사용자가 로그인하여 **토큰**을 획득
2. 클라이언트가 [client.connect()](/spreadjs/api/v19/collaboration/js-collaboration-client/classes/Client#connect)를 통해 **auth** 파라미터로 **토큰**을 서버에 전달
3. 서버가 **토큰**을 수신하여 검증 수행
   인증 성공 시 서버와의 연결이 설정되고, 실패 시 인증 실패 메시지가 반환됨

**서버**

1. 서버는 [server.use()](/spreadjs/api/v19/collaboration/js-collaboration/classes/Server#use) 미들웨어를 통해 클라이언트가 전달한 **토큰**을 조회하고 검증
2. 인증 성공 시 사용자 정보를 [connection.tags](/spreadjs/api/v19/collaboration/js-collaboration/classes/Connection#tags)에 저장하고, 이후 작업(예: 메시지 전송)을 허용
3. 인증 실패 시 서버는 오류 메시지를 반환하며, 클라이언트는 이를 수신할 수 있음

## **클라이언트 측: 인증 정보 전송**

프런트엔드에서는 연결을 설정할 때 토큰을 `auth` 파라미터로 서버에 전달해야 합니다. 아래는 클라이언트 측에서 인증 정보를 전송하는 예제입니다.
프런트엔드는 `client.connect()` 메서드를 통해 인증용 토큰을 서버에 전달합니다.

```typescript
import { Client } from '@mescius/js-collaboration-client';

// 로그인 시뮬레이션
async function login(username, password) {
    const response = await fetch('http://localhost:8080/login', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({ username, password })
    });
    const result = await response.json();
    if (result.success) {
        return result.token;
    } else {
        throw new Error(result.message);
    }
}

// 토큰 획득
const userToken = await login('reader1', 'read123');

// 클라이언트 인스턴스 생성 및 룸 연결
const client = new Client('ws://localhost:8080');
const connection = client.connect('room123', {
    auth: {
        token: userToken
    }
});
```

**코드 설명**

* 클라이언트는 `userToken`을 `auth` 파라미터로 서버에 전달하여 인증을 요청
* 서버는 미들웨어를 통해 토큰을 검증하고, 결과에 따라 연결을 허용하거나 거부

## 서버 측: 인증 및 메시지 권한

서버 측에서는 미들웨어를 사용하여 클라이언트가 전달한 토큰을 검증하고, 검증된 사용자 정보를 기반으로 권한을 처리합니다.

* `connect` 미들웨어에서 클라이언트가 전달한 토큰을 조회·검증하고, 사용자 정보를 `connection.tags`에 저장
* `message` 미들웨어에서 `connection.tags`에 저장된 사용자 정보를 조회하여 권한 검사를 수행

```typescript
import express from 'express';
import { createServer } from 'http';
import { Server } from '@mescius/js-collaboration';
import jwt from 'jsonwebtoken';

const app = express();
const httpServer = createServer(app);
const server = new Server({ httpServer });

app.use(express.json());

// JWT 비밀 키
const JWT_SECRET = 'your-secret-key-here';

// 사용자 데이터베이스 시뮬레이션
const users = new Map([
    ['admin1', { username: 'admin1', password: 'admin123', role: 'admin' }],
    ['reader1', { username: 'reader1', password: 'reader123', role: 'reader' }],
    ['editor1', { username: 'editor1', password: 'editor123', role: 'editor' }]
]);

// 로그인 라우트
app.post('/login', (req, res) => {
    const { username, password } = req.body;

    const user = users.get(username);
    if (!user || user.password !== password) {
        return res.status(401).json({
            success: false,
            message: '잘못된 사용자 이름 또는 비밀번호'
        });
    }

    const token = jwt.sign(
        { username: user.username, role: user.role },
        JWT_SECRET
    );

    res.json({
        success: true,
        token,
        user: { username: user.username, role: user.role }
    });
});

server.use('connect', async (context, next) => {
    const token = context.connection.auth?.token;

    if (!token) {
        await next('토큰이 제공되지 않았습니다');
        return;
    }

    try {
        const decoded = jwt.verify(token, JWT_SECRET);
        const user = {
            username: decoded.username,
            role: decoded.role
        };
        context.connection.tags.set('user', user);
        console.log(`사용자 인증 성공: ${user.username} (${user.role})`);
        await next();
    } catch {
        await next('유효하지 않은 토큰');
    }
});

server.use('message', async ({ connection }, next) => {
    const user = connection.tags.get('user');
    if (user.role === 'admin' || user.role === 'editor') {
        await next();
    } else {
        await next('편집 권한 없음');
    }
});

// 서버 시작
httpServer.listen(8080);
```

**코드 설명**

* **인증 검증**: `connect` 미들웨어에서 `context.connection.auth?.token`을 통해 전달된 토큰을 조회하고 검증합니다. 검증에 성공하면 사용자 정보를 조회하여 `connection.tags`에 저장합니다.
* **권한 검증**: `message` 미들웨어에서 `context.connection.tags.get('user')`로 저장된 사용자 정보를 조회하고, 사용자 역할에 따라 메시지 권한을 검증합니다.
