@runlot/auth 0.1.0-rc.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,37 @@
1
+ import { type User } from "./index.js";
2
+ export interface NextOpts {
3
+ next?: string;
4
+ }
5
+ /** 프로젝트가 등록할 수 있는 소셜 프로바이더 (docs/auth.md §9). */
6
+ export type AuthProvider = "github" | "google" | "kakao";
7
+ /** 로그인 페이지. 프로바이더를 주면 그 프로바이더의 authorize 로 바로 간다. */
8
+ export declare function signInUrl(provider?: AuthProvider, opts?: NextOpts): string;
9
+ /**
10
+ * 가입 페이지.
11
+ *
12
+ * **프로바이더 인자가 없다.** 소셜은 가입과 로그인이 한 길이라
13
+ * (docs/auth.md §5 — `GET sign-in/{provider}` 하나뿐이고 신원 upsert 가 §6 의
14
+ * 연결 규칙을 탄다) `signUpUrl("github")` 같은 주소는 서버에 없다. 소셜로
15
+ * 가입시키려면 `signInUrl(provider)` 를 쓴다.
16
+ */
17
+ export declare function signUpUrl(opts?: NextOpts): string;
18
+ /**
19
+ * 로그아웃.
20
+ *
21
+ * **링크가 아니라 POST 대상이다** (docs/auth.md §5). `<form method="post"
22
+ * action={signOutUrl()}>` 로 쓴다 — GET 으로 로그아웃되면 남의 페이지에 박힌
23
+ * `<img>` 하나가 사용자를 내보낼 수 있다.
24
+ */
25
+ export declare function signOutUrl(opts?: NextOpts): string;
26
+ /**
27
+ * 지금 로그인한 사람. 클라이언트 렌더 앱이 부팅할 때 부르는 자리다.
28
+ *
29
+ * 답이 200 이 아니면 `{ user: null }` 이다 — 로그아웃 상태는 예외가 아니라
30
+ * 답이고(401 도 답이다), 던지면 모든 페이지가 try/catch 가 된다.
31
+ *
32
+ * `fetchImpl` 은 시험이 가짜를 넣는 구멍이다.
33
+ */
34
+ export declare function session(fetchImpl?: typeof fetch): Promise<{
35
+ user: User | null;
36
+ }>;
37
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAGA,OAAO,EAAa,KAAK,IAAI,EAAE,MAAM,YAAY,CAAC;AAElD,MAAM,WAAW,QAAQ;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,iDAAiD;AACjD,MAAM,MAAM,YAAY,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;AAqBzD,qDAAqD;AACrD,wBAAgB,SAAS,CAAC,QAAQ,CAAC,EAAE,YAAY,EAAE,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM,CAE1E;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM,CAEjD;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,CAAC,EAAE,QAAQ,GAAG,MAAM,CAElD;AAED;;;;;;;GAOG;AACH,wBAAsB,OAAO,CAAC,SAAS,CAAC,EAAE,OAAO,KAAK,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,IAAI,GAAG,IAAI,CAAA;CAAE,CAAC,CAKtF"}
package/dist/client.js ADDED
@@ -0,0 +1,64 @@
1
+ // 브라우저 도우미 (docs/auth.md §10). 링크를 짓는 일과 `GET /session` 한 번이
2
+ // 전부라 의존성이 0 이고, 서버가 아는 것을 다시 안다고 주장하지 않는다.
3
+ import { AUTH_BASE } from "./index.js";
4
+ // `next` 는 같은 오리진의 상대 경로만 통과시킨다 — docs/auth.md §5 의 `safeNext`
5
+ // 와 같은 규칙이다. `/` 로 시작해야 하고 `//` 로 시작하면 안 된다: `//evil.example`
6
+ // 은 브라우저가 절대 URL 로 읽으므로 열린 리다이렉트가 된다.
7
+ //
8
+ // 여기서 거르는 것은 편의일 뿐 보안 경계가 아니다. 링크는 손으로도 만들 수 있으니
9
+ // 서버가 같은 검사를 다시 한다.
10
+ function safeNext(next) {
11
+ if (next === undefined || next === "")
12
+ return null;
13
+ if (!next.startsWith("/"))
14
+ return null;
15
+ if (next.startsWith("//"))
16
+ return null;
17
+ return next;
18
+ }
19
+ function authUrl(path, provider, opts) {
20
+ const base = `${AUTH_BASE}/${path}${provider ? `/${provider}` : ""}`;
21
+ const next = safeNext(opts?.next);
22
+ return next === null ? base : `${base}?next=${encodeURIComponent(next)}`;
23
+ }
24
+ /** 로그인 페이지. 프로바이더를 주면 그 프로바이더의 authorize 로 바로 간다. */
25
+ export function signInUrl(provider, opts) {
26
+ return authUrl("sign-in", provider, opts);
27
+ }
28
+ /**
29
+ * 가입 페이지.
30
+ *
31
+ * **프로바이더 인자가 없다.** 소셜은 가입과 로그인이 한 길이라
32
+ * (docs/auth.md §5 — `GET sign-in/{provider}` 하나뿐이고 신원 upsert 가 §6 의
33
+ * 연결 규칙을 탄다) `signUpUrl("github")` 같은 주소는 서버에 없다. 소셜로
34
+ * 가입시키려면 `signInUrl(provider)` 를 쓴다.
35
+ */
36
+ export function signUpUrl(opts) {
37
+ return authUrl("sign-up", undefined, opts);
38
+ }
39
+ /**
40
+ * 로그아웃.
41
+ *
42
+ * **링크가 아니라 POST 대상이다** (docs/auth.md §5). `<form method="post"
43
+ * action={signOutUrl()}>` 로 쓴다 — GET 으로 로그아웃되면 남의 페이지에 박힌
44
+ * `<img>` 하나가 사용자를 내보낼 수 있다.
45
+ */
46
+ export function signOutUrl(opts) {
47
+ return authUrl("sign-out", undefined, opts);
48
+ }
49
+ /**
50
+ * 지금 로그인한 사람. 클라이언트 렌더 앱이 부팅할 때 부르는 자리다.
51
+ *
52
+ * 답이 200 이 아니면 `{ user: null }` 이다 — 로그아웃 상태는 예외가 아니라
53
+ * 답이고(401 도 답이다), 던지면 모든 페이지가 try/catch 가 된다.
54
+ *
55
+ * `fetchImpl` 은 시험이 가짜를 넣는 구멍이다.
56
+ */
57
+ export async function session(fetchImpl) {
58
+ const f = fetchImpl ?? fetch;
59
+ const res = await f(`${AUTH_BASE}/session`, { credentials: "same-origin" });
60
+ if (!res.ok)
61
+ return { user: null };
62
+ return (await res.json());
63
+ }
64
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,6DAA6D;AAC7D,4CAA4C;AAE5C,OAAO,EAAE,SAAS,EAAa,MAAM,YAAY,CAAC;AASlD,+DAA+D;AAC/D,8DAA8D;AAC9D,sCAAsC;AACtC,EAAE;AACF,iDAAiD;AACjD,oBAAoB;AACpB,SAAS,QAAQ,CAAC,IAAwB;IACxC,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IACnD,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,OAAO,CAAC,IAAY,EAAE,QAAuB,EAAE,IAAe;IACrE,MAAM,IAAI,GAAG,GAAG,SAAS,IAAI,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IACrE,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAClC,OAAO,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,SAAS,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC;AAC3E,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,SAAS,CAAC,QAAuB,EAAE,IAAe;IAChE,OAAO,OAAO,CAAC,SAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;AAC5C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAC,IAAe;IACvC,OAAO,OAAO,CAAC,SAAS,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;AAC7C,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,IAAe;IACxC,OAAO,OAAO,CAAC,UAAU,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;AAC9C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAAC,SAAwB;IACpD,MAAM,CAAC,GAAG,SAAS,IAAI,KAAK,CAAC;IAC7B,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,GAAG,SAAS,UAAU,EAAE,EAAE,WAAW,EAAE,aAAa,EAAE,CAAC,CAAC;IAC5E,IAAI,CAAC,GAAG,CAAC,EAAE;QAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;IACnC,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA0B,CAAC;AACrD,CAAC"}
@@ -0,0 +1,92 @@
1
+ /** 시스템 워커가 종단하는 경로 접두 (docs/auth.md §5). */
2
+ export declare const AUTH_BASE = "/__runlot/auth";
3
+ export interface User {
4
+ /** uuid — `runlot_auth.users` 의 PK 이자 RLS 가 보는 값. */
5
+ id: string;
6
+ email: string | null;
7
+ emailVerified: boolean;
8
+ name: string | null;
9
+ avatarUrl: string | null;
10
+ /** 이 사용자에 연결된 로그인 수단. 예: `["password", "github"]`. */
11
+ providers: string[];
12
+ /** ISO 8601. */
13
+ createdAt: string;
14
+ /** ISO 8601. 한 번도 로그인하지 않았으면 `null`. */
15
+ lastSignInAt: string | null;
16
+ }
17
+ /**
18
+ * `signOut` 이 돌려주는 삭제 쿠키 한 줄. `HeadersInit` 으로 그대로 쓴다.
19
+ */
20
+ export interface SignOutHeaders {
21
+ "set-cookie": string;
22
+ }
23
+ /** `users.list` 의 한 쪽. `cursor` 가 `null` 이면 마지막 쪽이다. */
24
+ export interface UserPage {
25
+ users: User[];
26
+ cursor: string | null;
27
+ }
28
+ export interface UserUpdate {
29
+ name?: string | null;
30
+ avatarUrl?: string | null;
31
+ /** `true` 는 그 사용자의 세션 전부 폐기를 겸한다 (docs/auth.md §4). */
32
+ banned?: boolean;
33
+ }
34
+ /** 관리 조회·수정. 게이트웨이를 안 거치고 `DB` 바인딩으로 직접 읽는다. */
35
+ export interface AuthUsers {
36
+ get(id: string): Promise<User | null>;
37
+ getByEmail(email: string): Promise<User | null>;
38
+ list(opts?: {
39
+ cursor?: string;
40
+ limit?: number;
41
+ }): Promise<UserPage>;
42
+ update(id: string, patch: UserUpdate): Promise<User>;
43
+ /** 사용자·신원·세션·토큰. 앱의 표는 앱이 `ON DELETE` 로 잇는다. */
44
+ delete(id: string): Promise<void>;
45
+ }
46
+ export interface AuthSessions {
47
+ /** 비밀번호 변경 뒤의 관용구. */
48
+ revokeAll(userId: string): Promise<void>;
49
+ }
50
+ export interface AuthBinding {
51
+ /**
52
+ * 쿠키 → 세션 → 사용자. 없으면 `null`.
53
+ * **던지지 않는다** — 로그인 안 한 상태는 오류가 아니다.
54
+ */
55
+ user(request: Request): Promise<User | null>;
56
+ /**
57
+ * 그 세션을 지우고 삭제 쿠키를 돌려준다. 응답에 얹는 것은 호출자의 몫이다.
58
+ *
59
+ * ```ts
60
+ * return new Response(null, { headers: await env.auth.signOut(request) });
61
+ * ```
62
+ *
63
+ * `Headers` 가 아니라 평범한 객체인 이유는 실측이다 — `Headers` 는 RPC
64
+ * 경계를 못 넘는다 (docs/auth.md §4). 그래도 `HeadersInit` 이라 위처럼 그대로
65
+ * 얹힌다.
66
+ */
67
+ signOut(request: Request): Promise<SignOutHeaders>;
68
+ users: AuthUsers;
69
+ sessions: AuthSessions;
70
+ getUser?(id: string): Promise<User | null>;
71
+ getUserByEmail?(email: string): Promise<User | null>;
72
+ listUsers?(opts?: {
73
+ cursor?: string;
74
+ limit?: number;
75
+ }): Promise<UserPage>;
76
+ updateUser?(id: string, patch: UserUpdate): Promise<User>;
77
+ deleteUser?(id: string): Promise<void>;
78
+ revokeSessions?(userId: string): Promise<void>;
79
+ }
80
+ /**
81
+ * 자기 `Env` 에 붙이는 조각.
82
+ *
83
+ * ```ts
84
+ * interface Env extends AuthEnv {
85
+ * db: Fetcher;
86
+ * }
87
+ * ```
88
+ */
89
+ export interface AuthEnv {
90
+ auth: AuthBinding;
91
+ }
92
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ // `env.auth` 의 타입 (docs/auth.md §4, §10).
2
+ //
3
+ // 이 패키지는 런타임을 싣지 않는다. 바인딩은 workerd 안에서 auth 시스템 워커가
4
+ // 주입하므로, 사용자가 npm 에서 받을 것은 자기 `Env` 에 붙일 타입과 브라우저에서
5
+ // 부르는 도우미(`@runlot/auth/client`) 둘뿐이다. 의존성 0 인 이유도 같다 —
6
+ // 워커 번들에 한 바이트도 더하지 않아야 한다.
7
+ //
8
+ // `user(request)` 가 요청을 받는 것은 취향이 아니라 제약이다 (docs/access.md §4.2
9
+ // 의 논거 그대로): `env` 는 워커 수명 동안 고정된 묶음이고 신원은 요청마다 다른
10
+ // 값이다. 인자 없이 부르는 모양은 요청을 `AsyncLocalStorage` 로 숨겨 주는 회차 6
11
+ // 의 Next.js 어댑터 안에서만 성립하고, 그 밖에서는 `TypeError` 다 — "안 됨" 이
12
+ // 조용히 `null` 이 되면 모든 페이지가 로그아웃 상태로 보인다.
13
+ /** 시스템 워커가 종단하는 경로 접두 (docs/auth.md §5). */
14
+ export const AUTH_BASE = "/__runlot/auth";
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,0CAA0C;AAC1C,EAAE;AACF,oDAAoD;AACpD,oDAAoD;AACpD,wDAAwD;AACxD,4BAA4B;AAC5B,EAAE;AACF,gEAAgE;AAChE,mDAAmD;AACnD,0DAA0D;AAC1D,0DAA0D;AAC1D,wCAAwC;AAExC,4CAA4C;AAC5C,MAAM,CAAC,MAAM,SAAS,GAAG,gBAAgB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@runlot/auth",
3
+ "version": "0.1.0-rc.11",
4
+ "description": "env.auth 타입과 브라우저 도우미 — 런타임은 시스템 워커가 준다 (docs/auth.md §10)",
5
+ "publishConfig": {
6
+ "access": "public"
7
+ },
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/runlot/runlot",
11
+ "directory": "web/packages/auth"
12
+ },
13
+ "type": "module",
14
+ "main": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "types": "./dist/index.d.ts",
19
+ "default": "./dist/index.js"
20
+ },
21
+ "./client": {
22
+ "types": "./dist/client.d.ts",
23
+ "default": "./dist/client.js"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist"
28
+ ],
29
+ "devDependencies": {
30
+ "@types/node": "^22.10.2",
31
+ "esbuild": "^0.24.2",
32
+ "typescript": "^5.7.2",
33
+ "@runlot/config": "0.1.0-rc.11"
34
+ },
35
+ "scripts": {
36
+ "test": "node build.mjs",
37
+ "typecheck": "tsc --noEmit",
38
+ "build": "tsc -p tsconfig.build.json"
39
+ }
40
+ }