@gauts/auth 0.8.2 → 0.9.0
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.
- package/README.md +292 -66
- package/dist/adapters/drizzle/config.d.ts +11 -0
- package/dist/adapters/drizzle/config.d.ts.map +1 -0
- package/dist/adapters/drizzle/config.js +147 -0
- package/dist/adapters/drizzle/config.js.map +1 -0
- package/dist/adapters/drizzle/index.d.ts +4 -0
- package/dist/adapters/drizzle/index.d.ts.map +1 -0
- package/dist/adapters/drizzle/index.js +156 -0
- package/dist/adapters/drizzle/index.js.map +1 -0
- package/dist/adapters/drizzle/model.d.ts +21 -0
- package/dist/adapters/drizzle/model.d.ts.map +1 -0
- package/dist/adapters/drizzle/model.js +61 -0
- package/dist/adapters/drizzle/model.js.map +1 -0
- package/dist/adapters/drizzle/schema.d.ts +311 -0
- package/dist/adapters/drizzle/schema.d.ts.map +1 -0
- package/dist/adapters/drizzle/schema.js +37 -0
- package/dist/adapters/drizzle/schema.js.map +1 -0
- package/dist/adapters/drizzle/types.d.ts +109 -0
- package/dist/adapters/drizzle/types.d.ts.map +1 -0
- package/dist/adapters/drizzle/types.js +2 -0
- package/dist/adapters/drizzle/types.js.map +1 -0
- package/dist/adapters/express/index.d.ts +83 -0
- package/dist/adapters/express/index.d.ts.map +1 -0
- package/dist/adapters/express/index.js +72 -0
- package/dist/adapters/express/index.js.map +1 -0
- package/dist/adapters/express/social.d.ts +29 -0
- package/dist/adapters/express/social.d.ts.map +1 -0
- package/dist/adapters/express/social.js +44 -0
- package/dist/adapters/express/social.js.map +1 -0
- package/dist/adapters/fastify/index.d.ts +85 -0
- package/dist/adapters/fastify/index.d.ts.map +1 -0
- package/dist/adapters/fastify/index.js +100 -0
- package/dist/adapters/fastify/index.js.map +1 -0
- package/dist/adapters/fastify/social.d.ts +27 -0
- package/dist/adapters/fastify/social.d.ts.map +1 -0
- package/dist/adapters/fastify/social.js +48 -0
- package/dist/adapters/fastify/social.js.map +1 -0
- package/dist/adapters/hono/index.d.ts +4 -11
- package/dist/adapters/hono/index.d.ts.map +1 -1
- package/dist/adapters/hono/index.js +31 -223
- package/dist/adapters/hono/index.js.map +1 -1
- package/dist/adapters/hono/social.d.ts +4 -10
- package/dist/adapters/hono/social.d.ts.map +1 -1
- package/dist/adapters/hono/social.js +25 -221
- package/dist/adapters/hono/social.js.map +1 -1
- package/dist/adapters/model.d.ts +33 -0
- package/dist/adapters/model.d.ts.map +1 -0
- package/dist/adapters/model.js +87 -0
- package/dist/adapters/model.js.map +1 -0
- package/dist/adapters/prisma/config.d.ts.map +1 -1
- package/dist/adapters/prisma/config.js +1 -4
- package/dist/adapters/prisma/config.js.map +1 -1
- package/dist/adapters/prisma/model.d.ts +7 -8
- package/dist/adapters/prisma/model.d.ts.map +1 -1
- package/dist/adapters/prisma/model.js +11 -79
- package/dist/adapters/prisma/model.js.map +1 -1
- package/dist/adapters/utils/cookie.d.ts +17 -0
- package/dist/adapters/utils/cookie.d.ts.map +1 -0
- package/dist/adapters/utils/cookie.js +59 -0
- package/dist/adapters/utils/cookie.js.map +1 -0
- package/dist/adapters/utils/session.d.ts +4 -0
- package/dist/adapters/utils/session.d.ts.map +1 -0
- package/dist/adapters/utils/session.js +216 -0
- package/dist/adapters/utils/session.js.map +1 -0
- package/dist/adapters/utils/social.d.ts +51 -0
- package/dist/adapters/utils/social.d.ts.map +1 -0
- package/dist/adapters/utils/social.js +278 -0
- package/dist/adapters/utils/social.js.map +1 -0
- package/dist/adapters/utils/types.d.ts +67 -0
- package/dist/adapters/utils/types.d.ts.map +1 -0
- package/dist/adapters/utils/types.js +2 -0
- package/dist/adapters/utils/types.js.map +1 -0
- package/package.json +40 -3
- package/src/adapters/drizzle/schema.ts +47 -0
- package/src/adapters/prisma/schema.prisma +38 -0
|
@@ -1,211 +1,39 @@
|
|
|
1
|
-
import { deleteCookie, getCookie, setCookie } from "hono/cookie";
|
|
2
1
|
import { createAuth } from "../../auth.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { resolveHttpCookie } from "../utils/cookie.js";
|
|
3
|
+
import { requireSocialDb } from "../utils/social.js";
|
|
4
|
+
import { createHttpSession } from "../utils/session.js";
|
|
5
5
|
import { createHonoSocial } from "./social.js";
|
|
6
|
-
import { formatRenewAt, parseSessionToken, resolveSessionCookieNames, } from "../../session/cookie.js";
|
|
7
|
-
const resolveCookie = (config = {}) => {
|
|
8
|
-
const names = resolveSessionCookieNames(config);
|
|
9
|
-
const path = config.path ?? "/";
|
|
10
|
-
const sameSite = config.sameSite ?? "Lax";
|
|
11
|
-
const secure = config.secure ?? true;
|
|
12
|
-
if (!path.startsWith("/")) {
|
|
13
|
-
throw createError({
|
|
14
|
-
code: "AUTH_CONFIG_INVALID",
|
|
15
|
-
message: "Session cookie path must start with /.",
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
for (const name of Object.values(names)) {
|
|
19
|
-
if (name.startsWith("__Host-") && (!secure || path !== "/" || config.domain)) {
|
|
20
|
-
throw createError({
|
|
21
|
-
code: "AUTH_CONFIG_INVALID",
|
|
22
|
-
message: "__Host- cookies require secure=true, path=/, and no domain.",
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
if (name.startsWith("__Secure-") && !secure) {
|
|
26
|
-
throw createError({
|
|
27
|
-
code: "AUTH_CONFIG_INVALID",
|
|
28
|
-
message: "__Secure- cookies require secure=true.",
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
if (sameSite === "None" && !secure) {
|
|
33
|
-
throw createError({
|
|
34
|
-
code: "AUTH_CONFIG_INVALID",
|
|
35
|
-
message: "SameSite=None cookies require secure=true.",
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
return {
|
|
39
|
-
names,
|
|
40
|
-
options: {
|
|
41
|
-
...(config.domain ? { domain: config.domain } : {}),
|
|
42
|
-
httpOnly: true,
|
|
43
|
-
path,
|
|
44
|
-
sameSite,
|
|
45
|
-
secure,
|
|
46
|
-
},
|
|
47
|
-
};
|
|
48
|
-
};
|
|
49
|
-
const canUseCache = (method) => method === "GET" || method === "HEAD";
|
|
50
6
|
export const createHonoAdapter = ({ auth, cache, cookie, getIp, secret, }) => {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const resolved = resolveCookie(cookie);
|
|
64
|
-
let cacheService = null;
|
|
65
|
-
if (cache !== undefined) {
|
|
66
|
-
if (typeof secret !== "string") {
|
|
67
|
-
throw createError({
|
|
68
|
-
code: "AUTH_CONFIG_INVALID",
|
|
69
|
-
message: "Authentication secret is required when session cache is configured.",
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
cacheService = createSessionCache({
|
|
73
|
-
config: cache,
|
|
74
|
-
secret,
|
|
75
|
-
session: auth.config.session,
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
const getSessionClient = async (c) => ({
|
|
79
|
-
agent: c.req.header("user-agent") ?? null,
|
|
80
|
-
ip: getIp ? ((await getIp(c)) ?? null) : null,
|
|
81
|
-
platform: c.req.header("sec-ch-ua-platform") ?? null,
|
|
7
|
+
const session = createHttpSession({
|
|
8
|
+
appendSetCookie: ({ response, value }) => {
|
|
9
|
+
response.header("Set-Cookie", value, { append: true });
|
|
10
|
+
},
|
|
11
|
+
auth,
|
|
12
|
+
...(cache === undefined ? {} : { cache }),
|
|
13
|
+
...(cookie === undefined ? {} : { cookie }),
|
|
14
|
+
framework: "Hono",
|
|
15
|
+
getHeader: ({ name, request }) => request.req.header(name),
|
|
16
|
+
...(getIp === undefined ? {} : { getIp }),
|
|
17
|
+
getMethod: (request) => request.req.method,
|
|
18
|
+
...(secret === undefined ? {} : { secret }),
|
|
82
19
|
});
|
|
83
|
-
const getToken = (c) => {
|
|
84
|
-
return parseSessionToken(getCookie(c, resolved.names.sessionName));
|
|
85
|
-
};
|
|
86
|
-
const setSession = ({ context, session: value, token }) => {
|
|
87
|
-
setCookie(context, resolved.names.sessionName, token, {
|
|
88
|
-
...resolved.options,
|
|
89
|
-
expires: value.expires_at,
|
|
90
|
-
});
|
|
91
|
-
setCookie(context, resolved.names.renewName, formatRenewAt(value.renew_at), {
|
|
92
|
-
...resolved.options,
|
|
93
|
-
expires: value.expires_at,
|
|
94
|
-
});
|
|
95
|
-
};
|
|
96
|
-
const setCache = ({ context, resolved: value, token }) => {
|
|
97
|
-
if (!cacheService) {
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
const cached = cacheService.create({ resolved: value, token });
|
|
101
|
-
setCookie(context, resolved.names.cacheName, cached.value, {
|
|
102
|
-
...resolved.options,
|
|
103
|
-
expires: cached.expires_at,
|
|
104
|
-
});
|
|
105
|
-
};
|
|
106
|
-
const clearCache = (c) => {
|
|
107
|
-
if (cacheService) {
|
|
108
|
-
deleteCookie(c, resolved.names.cacheName, resolved.options);
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
20
|
const clearSession = (c) => {
|
|
112
|
-
|
|
113
|
-
deleteCookie(c, resolved.names.cacheName, resolved.options);
|
|
114
|
-
deleteCookie(c, resolved.names.renewName, resolved.options);
|
|
21
|
+
session.clearSession(c);
|
|
115
22
|
};
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
if (!token) {
|
|
119
|
-
clearSession(c);
|
|
120
|
-
throw createError({
|
|
121
|
-
code: "SESSION_INVALID",
|
|
122
|
-
message: "Authentication required.",
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
return token;
|
|
126
|
-
};
|
|
127
|
-
const resolveDbSession = async ({ client, context, token, }) => {
|
|
128
|
-
let value;
|
|
129
|
-
try {
|
|
130
|
-
value = await auth.session.resolve({ client, token });
|
|
131
|
-
}
|
|
132
|
-
catch (error) {
|
|
133
|
-
if (isAuthError(error) && error.code === "SESSION_CLIENT_MISMATCH") {
|
|
134
|
-
clearSession(context);
|
|
135
|
-
}
|
|
136
|
-
throw error;
|
|
137
|
-
}
|
|
138
|
-
if (!value) {
|
|
139
|
-
clearSession(context);
|
|
140
|
-
throw createError({
|
|
141
|
-
code: "SESSION_INVALID",
|
|
142
|
-
message: "Authentication required.",
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
return value;
|
|
146
|
-
};
|
|
147
|
-
const createSession = async ({ account_id, context, country, }) => {
|
|
148
|
-
const created = await auth.session.create({
|
|
23
|
+
const createSession = ({ account_id, context, country, }) => {
|
|
24
|
+
return session.createSession({
|
|
149
25
|
account_id,
|
|
150
|
-
client: await getSessionClient(context),
|
|
151
26
|
...(country === undefined ? {} : { country }),
|
|
27
|
+
request: context,
|
|
28
|
+
response: context,
|
|
152
29
|
});
|
|
153
|
-
const value = {
|
|
154
|
-
account: created.account,
|
|
155
|
-
session: created.session,
|
|
156
|
-
};
|
|
157
|
-
setSession({ context, session: created.session, token: created.token });
|
|
158
|
-
setCache({ context, resolved: value, token: created.token });
|
|
159
|
-
return created.session;
|
|
160
30
|
};
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const useCache = canUseCache(c.req.method);
|
|
165
|
-
if (cacheService && useCache) {
|
|
166
|
-
const cached = cacheService.resolve({
|
|
167
|
-
client,
|
|
168
|
-
token,
|
|
169
|
-
value: getCookie(c, resolved.names.cacheName),
|
|
170
|
-
});
|
|
171
|
-
if (cached) {
|
|
172
|
-
return cached;
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
const value = await resolveDbSession({ client, context: c, token });
|
|
176
|
-
if (cacheService && useCache) {
|
|
177
|
-
setCache({ context: c, resolved: value, token });
|
|
178
|
-
}
|
|
179
|
-
else {
|
|
180
|
-
clearCache(c);
|
|
181
|
-
}
|
|
182
|
-
return value;
|
|
31
|
+
const getToken = (c) => session.getToken(c);
|
|
32
|
+
const renewSession = (c) => {
|
|
33
|
+
return session.renewSession({ request: c, response: c });
|
|
183
34
|
};
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
let renewed;
|
|
187
|
-
try {
|
|
188
|
-
renewed = await auth.session.renew({
|
|
189
|
-
client: await getSessionClient(c),
|
|
190
|
-
token,
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
catch (error) {
|
|
194
|
-
if (isAuthError(error) && error.code === "SESSION_CLIENT_MISMATCH") {
|
|
195
|
-
clearSession(c);
|
|
196
|
-
}
|
|
197
|
-
throw error;
|
|
198
|
-
}
|
|
199
|
-
if (!renewed) {
|
|
200
|
-
clearSession(c);
|
|
201
|
-
throw createError({
|
|
202
|
-
code: "SESSION_INVALID",
|
|
203
|
-
message: "Authentication required.",
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
setSession({ context: c, session: renewed.session, token });
|
|
207
|
-
setCache({ context: c, resolved: renewed, token });
|
|
208
|
-
return renewed.session;
|
|
35
|
+
const resolveSession = (c) => {
|
|
36
|
+
return session.resolveSession({ request: c, response: c });
|
|
209
37
|
};
|
|
210
38
|
const requireSession = async (c, next) => {
|
|
211
39
|
const value = await resolveSession(c);
|
|
@@ -214,15 +42,12 @@ export const createHonoAdapter = ({ auth, cache, cookie, getIp, secret, }) => {
|
|
|
214
42
|
c.set("user", value.account.user);
|
|
215
43
|
await next();
|
|
216
44
|
};
|
|
217
|
-
const revokeSession =
|
|
218
|
-
|
|
219
|
-
const revoked = token ? await auth.session.revokeToken(token) : [];
|
|
220
|
-
clearSession(c);
|
|
221
|
-
return revoked;
|
|
45
|
+
const revokeSession = (c) => {
|
|
46
|
+
return session.revokeSession({ request: c, response: c });
|
|
222
47
|
};
|
|
223
48
|
return {
|
|
224
49
|
clearSession,
|
|
225
|
-
cookie:
|
|
50
|
+
cookie: session.cookie,
|
|
226
51
|
createSession,
|
|
227
52
|
getToken,
|
|
228
53
|
renewSession,
|
|
@@ -245,33 +70,16 @@ const createHonoAuthImpl = ({ cache, cookie, db, getIp, password, secret, sessio
|
|
|
245
70
|
...(secret === undefined ? {} : { secret }),
|
|
246
71
|
});
|
|
247
72
|
if (social === undefined) {
|
|
248
|
-
return {
|
|
249
|
-
...auth,
|
|
250
|
-
...adapter,
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
const socialDb = db;
|
|
254
|
-
const methods = [
|
|
255
|
-
"createAccount",
|
|
256
|
-
"createSocial",
|
|
257
|
-
"findAccount",
|
|
258
|
-
"findEmail",
|
|
259
|
-
"findSocial",
|
|
260
|
-
];
|
|
261
|
-
if (methods.some((method) => typeof socialDb[method] !== "function")) {
|
|
262
|
-
throw createError({
|
|
263
|
-
code: "AUTH_CONFIG_INVALID",
|
|
264
|
-
message: "DB adapter does not support social authentication.",
|
|
265
|
-
});
|
|
73
|
+
return { ...auth, ...adapter };
|
|
266
74
|
}
|
|
267
|
-
const resolvedCookie =
|
|
75
|
+
const resolvedCookie = resolveHttpCookie(cookie);
|
|
268
76
|
return {
|
|
269
77
|
...auth,
|
|
270
78
|
...adapter,
|
|
271
79
|
social: createHonoSocial({
|
|
272
80
|
config: social,
|
|
273
81
|
cookie: resolvedCookie.options,
|
|
274
|
-
db:
|
|
82
|
+
db: requireSocialDb(db),
|
|
275
83
|
sessionCookieNames: Object.values(resolvedCookie.names),
|
|
276
84
|
secret,
|
|
277
85
|
}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/adapters/hono/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/adapters/hono/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAA4B,MAAM,eAAe,CAAC;AAKrE,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAExD,OAAO,EAAE,gBAAgB,EAAmB,MAAM,aAAa,CAAC;AAgGhE,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAA+B,EAC5D,IAAI,EACJ,KAAK,EACL,MAAM,EACN,KAAK,EACL,MAAM,GACoB,EAAyB,EAAE;IACrD,MAAM,OAAO,GAAG,iBAAiB,CAA6B;QAC1D,eAAe,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAE;YACrC,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3D,CAAC;QACD,IAAI;QACJ,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;QACzC,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;QAC3C,SAAS,EAAE,MAAM;QACjB,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;QAC1D,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;QACzC,SAAS,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM;QAC1C,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;KAC9C,CAAC,CAAC;IAEH,MAAM,YAAY,GAAG,CAAC,CAAU,EAAQ,EAAE;QACtC,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC5B,CAAC,CAAC;IAEF,MAAM,aAAa,GAAG,CAAC,EACnB,UAAU,EACV,OAAO,EACP,OAAO,GACU,EAAoB,EAAE;QACvC,OAAO,OAAO,CAAC,aAAa,CAAC;YACzB,UAAU;YACV,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;YAC7C,OAAO,EAAE,OAAO;YAChB,QAAQ,EAAE,OAAO;SACpB,CAAC,CAAC;IACP,CAAC,CAAC;IAEF,MAAM,QAAQ,GAAG,CAAC,CAAU,EAAiB,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IAEpE,MAAM,YAAY,GAAG,CAAC,CAAU,EAAoB,EAAE;QAClD,OAAO,OAAO,CAAC,YAAY,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;IAC7D,CAAC,CAAC;IAEF,MAAM,cAAc,GAAG,CAAC,CAAU,EAAsC,EAAE;QACtE,OAAO,OAAO,CAAC,cAAc,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;IAC/D,CAAC,CAAC;IAEF,MAAM,cAAc,GAA6C,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;QAC/E,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,CAAC,CAAC,CAAC;QAEtC,CAAC,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QAChC,CAAC,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;QAChC,CAAC,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAClC,MAAM,IAAI,EAAE,CAAC;IACjB,CAAC,CAAC;IAEF,MAAM,aAAa,GAAG,CAAC,CAAU,EAAqB,EAAE;QACpD,OAAO,OAAO,CAAC,aAAa,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;IAC9D,CAAC,CAAC;IAEF,OAAO;QACH,YAAY;QACZ,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,aAAa;QACb,QAAQ;QACR,YAAY;QACZ,cAAc;QACd,cAAc;QACd,aAAa;KAChB,CAAC;AACN,CAAC,CAAC;AAkBF,MAAM,kBAAkB,GAAG,CAAkD,EACzE,KAAK,EACL,MAAM,EACN,EAAE,EACF,KAAK,EACL,QAAQ,EACR,MAAM,EACN,OAAO,EACP,MAAM,GACwB,EAAwD,EAAE;IACxF,MAAM,IAAI,GAAG,UAAU,CAAC;QACpB,EAAE;QACF,GAAG,CAAC,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC;QAC/C,GAAG,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC;KAChD,CAAC,CAAC;IACH,MAAM,OAAO,GAAG,iBAAiB,CAAC;QAC9B,IAAI;QACJ,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;QACzC,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;QAC3C,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC;QACzC,GAAG,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC;KAC9C,CAAC,CAAC;IAEH,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACvB,OAAO,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,EAAE,CAAC;IACnC,CAAC;IAED,MAAM,cAAc,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAAC;IAEjD,OAAO;QACH,GAAG,IAAI;QACP,GAAG,OAAO;QACV,MAAM,EAAE,gBAAgB,CAAC;YACrB,MAAM,EAAE,MAAM;YACd,MAAM,EAAE,cAAc,CAAC,OAAO;YAC9B,EAAE,EAAE,eAAe,CAAC,EAAE,CAAC;YACvB,kBAAkB,EAAE,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC;YACvD,MAAM;SACT,CAAC;KACL,CAAC;AACN,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG,kBAAoC,CAAC"}
|
|
@@ -1,16 +1,10 @@
|
|
|
1
1
|
import type { Context, Env, MiddlewareHandler } from "hono";
|
|
2
|
-
import type { SocialAuthenticated, SocialConfig, SocialDbAdapter, SocialIdentity } from "../../social/types.js";
|
|
3
2
|
import type { AuthAccount } from "../../session/types.js";
|
|
4
|
-
type
|
|
5
|
-
|
|
6
|
-
httpOnly: true;
|
|
7
|
-
path: string;
|
|
8
|
-
sameSite: "Strict" | "Lax" | "None";
|
|
9
|
-
secure: boolean;
|
|
10
|
-
};
|
|
3
|
+
import type { SocialAuthenticated, SocialConfig, SocialDbAdapter, SocialIdentity } from "../../social/types.js";
|
|
4
|
+
import type { HttpCookieOptions } from "../utils/types.js";
|
|
11
5
|
type HonoSocialDeps<TAccount extends AuthAccount, TData> = {
|
|
12
6
|
config: SocialConfig<TData>;
|
|
13
|
-
cookie:
|
|
7
|
+
cookie: HttpCookieOptions;
|
|
14
8
|
db: SocialDbAdapter<TAccount>;
|
|
15
9
|
sessionCookieNames: readonly string[];
|
|
16
10
|
secret: string;
|
|
@@ -30,6 +24,6 @@ export type HonoSocial<TAccount extends AuthAccount = AuthAccount, TData = undef
|
|
|
30
24
|
getRegistration(c: Context): SocialIdentity;
|
|
31
25
|
handle: MiddlewareHandler<HonoSocialEnv<TAccount>>;
|
|
32
26
|
};
|
|
33
|
-
export declare const createHonoSocial: <TAccount extends AuthAccount, TData>({ config
|
|
27
|
+
export declare const createHonoSocial: <TAccount extends AuthAccount, TData>({ config, cookie, db, sessionCookieNames, secret, }: HonoSocialDeps<TAccount, TData>) => HonoSocial<TAccount, TData>;
|
|
34
28
|
export {};
|
|
35
29
|
//# sourceMappingURL=social.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"social.d.ts","sourceRoot":"","sources":["../../../src/adapters/hono/social.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"social.d.ts","sourceRoot":"","sources":["../../../src/adapters/hono/social.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAC5D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,KAAK,EACR,mBAAmB,EACnB,YAAY,EACZ,eAAe,EACf,cAAc,EACjB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,KAAK,cAAc,CAAC,QAAQ,SAAS,WAAW,EAAE,KAAK,IAAI;IACvD,MAAM,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC;IAC5B,MAAM,EAAE,iBAAiB,CAAC;IAC1B,EAAE,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAC;IAC9B,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,MAAM,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,KAAK,yBAAyB,CAAC,KAAK,IAAI;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,KAAK,CAAC;CACf,CAAC;AAEF,MAAM,MAAM,mBAAmB,CAAC,QAAQ,SAAS,WAAW,GAAG,WAAW,IAAI;IAC1E,MAAM,EAAE,mBAAmB,CAAC,QAAQ,CAAC,CAAC;CACzC,CAAC;AAEF,MAAM,MAAM,aAAa,CAAC,QAAQ,SAAS,WAAW,GAAG,WAAW,IAAI,GAAG,GAAG;IAC1E,SAAS,EAAE,mBAAmB,CAAC,QAAQ,CAAC,CAAC;CAC5C,CAAC;AAEF,MAAM,MAAM,UAAU,CAAC,QAAQ,SAAS,WAAW,GAAG,WAAW,EAAE,KAAK,GAAG,SAAS,IAAI;IACpF,oBAAoB,CAChB,KAAK,EAAE,yBAAyB,CAAC,KAAK,CAAC,GACxC,OAAO,CAAC,mBAAmB,CAAC,QAAQ,CAAC,CAAC,CAAC;IAC1C,eAAe,CAAC,CAAC,EAAE,OAAO,GAAG,cAAc,CAAC;IAC5C,MAAM,EAAE,iBAAiB,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC,CAAC;CACtD,CAAC;AAEF,eAAO,MAAM,gBAAgB,GAAI,QAAQ,SAAS,WAAW,EAAE,KAAK,EAAE,qDAMnE,cAAc,CAAC,QAAQ,EAAE,KAAK,CAAC,KAAG,UAAU,CAAC,QAAQ,EAAE,KAAK,CAkD9D,CAAC"}
|
|
@@ -1,234 +1,38 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"SOCIAL_ACCOUNT_NOT_FOUND",
|
|
10
|
-
"SOCIAL_EMAIL_INVALID",
|
|
11
|
-
"SOCIAL_EMAIL_MISMATCH",
|
|
12
|
-
"SOCIAL_PROVIDER_ERROR",
|
|
13
|
-
"SOCIAL_REGISTRATION_INVALID",
|
|
14
|
-
"SOCIAL_STATE_INVALID",
|
|
15
|
-
]);
|
|
16
|
-
const getProviderId = (value) => {
|
|
17
|
-
return value === "github" || value === "google" || value === "x" ? value : null;
|
|
18
|
-
};
|
|
19
|
-
const getIntent = (value) => {
|
|
20
|
-
if (value === undefined || value === "login") {
|
|
21
|
-
return "login";
|
|
22
|
-
}
|
|
23
|
-
if (value === "register") {
|
|
24
|
-
return value;
|
|
25
|
-
}
|
|
26
|
-
throw createError({
|
|
27
|
-
code: "SOCIAL_STATE_INVALID",
|
|
28
|
-
message: "Social authentication intent is invalid.",
|
|
29
|
-
});
|
|
30
|
-
};
|
|
31
|
-
export const createHonoSocial = ({ config: input, cookie, db, sessionCookieNames, secret, }) => {
|
|
32
|
-
validateSocialSecret(secret);
|
|
33
|
-
if (cookie.sameSite === "Strict") {
|
|
34
|
-
throw createError({
|
|
35
|
-
code: "AUTH_CONFIG_INVALID",
|
|
36
|
-
message: "Social authentication requires SameSite=Lax or SameSite=None cookies.",
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
const config = resolveSocialConfig(input);
|
|
40
|
-
if (sessionCookieNames.includes(config.cookieName)) {
|
|
41
|
-
throw createError({
|
|
42
|
-
code: "AUTH_CONFIG_INVALID",
|
|
43
|
-
message: "Social and session cookie names must be unique.",
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
if (config.cookieName.startsWith("__Host-") &&
|
|
47
|
-
(!cookie.secure || cookie.path !== "/" || cookie.domain)) {
|
|
48
|
-
throw createError({
|
|
49
|
-
code: "AUTH_CONFIG_INVALID",
|
|
50
|
-
message: "__Host- cookies require secure=true, path=/, and no domain.",
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
if (config.cookieName.startsWith("__Secure-") && !cookie.secure) {
|
|
54
|
-
throw createError({
|
|
55
|
-
code: "AUTH_CONFIG_INVALID",
|
|
56
|
-
message: "__Secure- cookies require secure=true.",
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
const service = createSocialService({
|
|
1
|
+
import { createHttpSocial } from "../utils/social.js";
|
|
2
|
+
export const createHonoSocial = ({ config, cookie, db, sessionCookieNames, secret, }) => {
|
|
3
|
+
const social = createHttpSocial({
|
|
4
|
+
appendSetCookie: ({ response, value }) => {
|
|
5
|
+
response.header("Set-Cookie", value, { append: true });
|
|
6
|
+
},
|
|
7
|
+
config,
|
|
8
|
+
cookie,
|
|
60
9
|
db,
|
|
61
|
-
|
|
10
|
+
getHeader: ({ name, request }) => request.req.header(name),
|
|
11
|
+
getParam: ({ name, request }) => request.req.param(name),
|
|
12
|
+
getQuery: ({ name, request }) => request.req.query(name),
|
|
13
|
+
sessionCookieNames,
|
|
14
|
+
secret,
|
|
62
15
|
});
|
|
63
|
-
const clearSocial = (c) => {
|
|
64
|
-
deleteCookie(c, config.cookieName, cookie);
|
|
65
|
-
};
|
|
66
|
-
const setSocial = ({ context, expiresAt, value }) => {
|
|
67
|
-
setCookie(context, config.cookieName, value, {
|
|
68
|
-
...cookie,
|
|
69
|
-
expires: expiresAt,
|
|
70
|
-
});
|
|
71
|
-
};
|
|
72
|
-
const requireProvider = (value) => {
|
|
73
|
-
const id = getProviderId(value);
|
|
74
|
-
const provider = id ? config.providers.get(id) : undefined;
|
|
75
|
-
if (!provider) {
|
|
76
|
-
throw createError({
|
|
77
|
-
code: "SOCIAL_PROVIDER_ERROR",
|
|
78
|
-
message: "Social provider is not configured.",
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
return provider;
|
|
82
|
-
};
|
|
83
|
-
const fail = ({ context, error, errorTo }) => {
|
|
84
|
-
if (!isAuthError(error) || !SOCIAL_ERROR_CODES.has(error.code)) {
|
|
85
|
-
throw error;
|
|
86
|
-
}
|
|
87
|
-
clearSocial(context);
|
|
88
|
-
return context.redirect(addSocialError({ code: error.code, path: errorTo }));
|
|
89
|
-
};
|
|
90
|
-
const authenticate = async ({ context, intent, navigation, provider, verifier, }) => {
|
|
91
|
-
const code = context.req.query("code");
|
|
92
|
-
const providerError = context.req.query("error");
|
|
93
|
-
if (providerError || !code) {
|
|
94
|
-
throw createError({
|
|
95
|
-
code: "SOCIAL_PROVIDER_ERROR",
|
|
96
|
-
message: "Social provider denied authentication.",
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
const identity = await provider.getIdentity({
|
|
100
|
-
code,
|
|
101
|
-
codeVerifier: verifier,
|
|
102
|
-
});
|
|
103
|
-
const account = await service.find(identity);
|
|
104
|
-
if (account) {
|
|
105
|
-
return {
|
|
106
|
-
account,
|
|
107
|
-
identity,
|
|
108
|
-
registered: false,
|
|
109
|
-
returnTo: navigation.returnTo,
|
|
110
|
-
};
|
|
111
|
-
}
|
|
112
|
-
if (intent !== "register" || !config.registration) {
|
|
113
|
-
throw createError({
|
|
114
|
-
code: "SOCIAL_ACCOUNT_NOT_FOUND",
|
|
115
|
-
message: "Social account does not exist.",
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
if (navigation.registerTo) {
|
|
119
|
-
if (!config.registration.createAccount) {
|
|
120
|
-
throw createError({
|
|
121
|
-
code: "SOCIAL_REGISTRATION_INVALID",
|
|
122
|
-
message: "Custom social registration is not configured.",
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
const pending = createRegistrationState({ identity, navigation, secret });
|
|
126
|
-
setSocial({
|
|
127
|
-
context,
|
|
128
|
-
expiresAt: pending.expires_at,
|
|
129
|
-
value: pending.value,
|
|
130
|
-
});
|
|
131
|
-
return context.redirect(navigation.registerTo);
|
|
132
|
-
}
|
|
133
|
-
const created = await service.register({
|
|
134
|
-
data: undefined,
|
|
135
|
-
identity,
|
|
136
|
-
});
|
|
137
|
-
return {
|
|
138
|
-
account: created,
|
|
139
|
-
identity,
|
|
140
|
-
registered: true,
|
|
141
|
-
returnTo: navigation.returnTo,
|
|
142
|
-
};
|
|
143
|
-
};
|
|
144
16
|
const handle = async (c, next) => {
|
|
145
|
-
const
|
|
146
|
-
if (
|
|
17
|
+
const result = await social.handle({ request: c, response: c });
|
|
18
|
+
if (result.type === "not_found") {
|
|
147
19
|
return c.notFound();
|
|
148
20
|
}
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const navigation = resolveSocialNavigation({
|
|
152
|
-
errorTo: c.req.query("errorTo"),
|
|
153
|
-
registerTo: c.req.query("registerTo"),
|
|
154
|
-
returnTo: c.req.query("returnTo"),
|
|
155
|
-
});
|
|
156
|
-
const created = createOAuthState({
|
|
157
|
-
intent: getIntent(c.req.query("intent")),
|
|
158
|
-
navigation,
|
|
159
|
-
provider: provider.id,
|
|
160
|
-
secret,
|
|
161
|
-
});
|
|
162
|
-
setSocial({
|
|
163
|
-
context: c,
|
|
164
|
-
expiresAt: created.expires_at,
|
|
165
|
-
value: created.value,
|
|
166
|
-
});
|
|
167
|
-
return c.redirect(provider.getAuthorizationUrl({
|
|
168
|
-
codeChallenge: created.codeChallenge,
|
|
169
|
-
state: created.state,
|
|
170
|
-
}));
|
|
171
|
-
}
|
|
172
|
-
const stored = resolveOAuthState({
|
|
173
|
-
provider: provider.id,
|
|
174
|
-
secret,
|
|
175
|
-
state: c.req.query("state"),
|
|
176
|
-
value: getCookie(c, config.cookieName),
|
|
177
|
-
});
|
|
178
|
-
if (!stored) {
|
|
179
|
-
clearSocial(c);
|
|
180
|
-
throw createError({
|
|
181
|
-
code: "SOCIAL_STATE_INVALID",
|
|
182
|
-
message: "Social authentication state is invalid or expired.",
|
|
183
|
-
});
|
|
21
|
+
if (result.type === "redirect") {
|
|
22
|
+
return c.redirect(result.url);
|
|
184
23
|
}
|
|
185
|
-
|
|
186
|
-
try {
|
|
187
|
-
authenticated = await authenticate({
|
|
188
|
-
context: c,
|
|
189
|
-
intent: stored.intent,
|
|
190
|
-
navigation: stored,
|
|
191
|
-
provider,
|
|
192
|
-
verifier: stored.verifier,
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
|
-
catch (error) {
|
|
196
|
-
return fail({ context: c, error, errorTo: stored.errorTo });
|
|
197
|
-
}
|
|
198
|
-
if (authenticated instanceof Response) {
|
|
199
|
-
return authenticated;
|
|
200
|
-
}
|
|
201
|
-
clearSocial(c);
|
|
202
|
-
c.set("social", authenticated);
|
|
24
|
+
c.set("social", result.value);
|
|
203
25
|
await next();
|
|
204
26
|
};
|
|
205
|
-
const requireRegistration = (c) => {
|
|
206
|
-
const registration = resolveRegistrationState({
|
|
207
|
-
secret,
|
|
208
|
-
value: getCookie(c, config.cookieName),
|
|
209
|
-
});
|
|
210
|
-
if (!registration) {
|
|
211
|
-
clearSocial(c);
|
|
212
|
-
throw createError({
|
|
213
|
-
code: "SOCIAL_STATE_INVALID",
|
|
214
|
-
message: "Social registration is invalid or expired.",
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
return registration;
|
|
218
|
-
};
|
|
219
27
|
const getRegistration = (c) => {
|
|
220
|
-
return
|
|
28
|
+
return social.getRegistration({ request: c, response: c });
|
|
221
29
|
};
|
|
222
|
-
const completeRegistration =
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
identity: registration.identity,
|
|
229
|
-
registered: true,
|
|
230
|
-
returnTo: registration.navigation.returnTo,
|
|
231
|
-
};
|
|
30
|
+
const completeRegistration = ({ context, data, }) => {
|
|
31
|
+
return social.completeRegistration({
|
|
32
|
+
data,
|
|
33
|
+
request: context,
|
|
34
|
+
response: context,
|
|
35
|
+
});
|
|
232
36
|
};
|
|
233
37
|
return {
|
|
234
38
|
completeRegistration,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"social.js","sourceRoot":"","sources":["../../../src/adapters/hono/social.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"social.js","sourceRoot":"","sources":["../../../src/adapters/hono/social.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAgCtD,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAsC,EAClE,MAAM,EACN,MAAM,EACN,EAAE,EACF,kBAAkB,EAClB,MAAM,GACwB,EAA+B,EAAE;IAC/D,MAAM,MAAM,GAAG,gBAAgB,CAAoC;QAC/D,eAAe,EAAE,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,EAAE;YACrC,QAAQ,CAAC,MAAM,CAAC,YAAY,EAAE,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3D,CAAC;QACD,MAAM;QACN,MAAM;QACN,EAAE;QACF,SAAS,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC;QAC1D,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;QACxD,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC;QACxD,kBAAkB;QAClB,MAAM;KACT,CAAC,CAAC;IAEH,MAAM,MAAM,GAA+C,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;QACzE,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;QAEhE,IAAI,MAAM,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;YAC9B,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;QACxB,CAAC;QAED,IAAI,MAAM,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;YAC7B,OAAO,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAClC,CAAC;QAED,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;QAC9B,MAAM,IAAI,EAAE,CAAC;IACjB,CAAC,CAAC;IAEF,MAAM,eAAe,GAAG,CAAC,CAAU,EAAkB,EAAE;QACnD,OAAO,MAAM,CAAC,eAAe,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC,CAAC;IAC/D,CAAC,CAAC;IAEF,MAAM,oBAAoB,GAAG,CAAC,EAC1B,OAAO,EACP,IAAI,GAC2B,EAA0C,EAAE;QAC3E,OAAO,MAAM,CAAC,oBAAoB,CAAC;YAC/B,IAAI;YACJ,OAAO,EAAE,OAAO;YAChB,QAAQ,EAAE,OAAO;SACpB,CAAC,CAAC;IACP,CAAC,CAAC;IAEF,OAAO;QACH,oBAAoB;QACpB,eAAe;QACf,MAAM;KACT,CAAC;AACN,CAAC,CAAC"}
|