mirror of
https://github.com/ourongxing/newsnow.git
synced 2025-01-31 10:58:04 +08:00
fix: github oauth on cloudflare page
This commit is contained in:
parent
8d8bc41691
commit
4607758dd0
@ -15,11 +15,12 @@
|
|||||||
"build": "vite build",
|
"build": "vite build",
|
||||||
"lint": "eslint",
|
"lint": "eslint",
|
||||||
"favicon": "tsx ./scripts/favicon.ts",
|
"favicon": "tsx ./scripts/favicon.ts",
|
||||||
"start": "PORT=4444 node --env-file .env.vars dist/output/server/index.mjs",
|
"start": "PORT=4444 node --env-file .env.server dist/output/server/index.mjs",
|
||||||
"preview": "CF_PAGES=1 pnpm run build && wrangler pages dev",
|
"preview": "CF_PAGES=1 pnpm run build && wrangler pages dev",
|
||||||
"deploy": "CF_PAGES=1 pnpm run build && wrangler pages deploy",
|
"deploy": "CF_PAGES=1 pnpm run build && wrangler pages deploy",
|
||||||
"release": "bumpp",
|
"release": "bumpp",
|
||||||
"prepare": "simple-git-hooks",
|
"prepare": "simple-git-hooks",
|
||||||
|
"log": "wrangler pages deployment tail --project-name newsnow",
|
||||||
"test": "vitest -c vitest.config.ts"
|
"test": "vitest -c vitest.config.ts"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@ -34,6 +35,7 @@
|
|||||||
"cheerio": "^1.0.0",
|
"cheerio": "^1.0.0",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"consola": "^3.2.3",
|
"consola": "^3.2.3",
|
||||||
|
"cookie-es": "^1.2.2",
|
||||||
"dayjs": "1.11.13",
|
"dayjs": "1.11.13",
|
||||||
"db0": "npm:@ourongxing/db0@0.1.6",
|
"db0": "npm:@ourongxing/db0@0.1.6",
|
||||||
"defu": "^6.1.4",
|
"defu": "^6.1.4",
|
||||||
@ -96,7 +98,8 @@
|
|||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"dayjs": "1.11.13",
|
"dayjs": "1.11.13",
|
||||||
"db0": "npm:@ourongxing/db0@0.1.6"
|
"db0": "npm:@ourongxing/db0@0.1.6",
|
||||||
|
"nitropack": "^2.9.7"
|
||||||
},
|
},
|
||||||
"simple-git-hooks": {
|
"simple-git-hooks": {
|
||||||
"pre-commit": "npx lint-staged"
|
"pre-commit": "npx lint-staged"
|
||||||
|
20
pnpm-lock.yaml
generated
20
pnpm-lock.yaml
generated
@ -7,6 +7,7 @@ settings:
|
|||||||
overrides:
|
overrides:
|
||||||
dayjs: 1.11.13
|
dayjs: 1.11.13
|
||||||
db0: npm:@ourongxing/db0@0.1.6
|
db0: npm:@ourongxing/db0@0.1.6
|
||||||
|
nitropack: ^2.9.7
|
||||||
|
|
||||||
patchedDependencies:
|
patchedDependencies:
|
||||||
dayjs:
|
dayjs:
|
||||||
@ -50,6 +51,9 @@ importers:
|
|||||||
consola:
|
consola:
|
||||||
specifier: ^3.2.3
|
specifier: ^3.2.3
|
||||||
version: 3.2.3
|
version: 3.2.3
|
||||||
|
cookie-es:
|
||||||
|
specifier: ^1.2.2
|
||||||
|
version: 1.2.2
|
||||||
dayjs:
|
dayjs:
|
||||||
specifier: 1.11.13
|
specifier: 1.11.13
|
||||||
version: 1.11.13(patch_hash=vxjypqxmsykboavgqknf3tdbfa)
|
version: 1.11.13(patch_hash=vxjypqxmsykboavgqknf3tdbfa)
|
||||||
@ -2514,8 +2518,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==}
|
resolution: {integrity: sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==}
|
||||||
engines: {node: '>= 14'}
|
engines: {node: '>= 14'}
|
||||||
|
|
||||||
croner@8.1.1:
|
croner@8.1.2:
|
||||||
resolution: {integrity: sha512-1VdUuRnQP4drdFkS8NKvDR1NBgevm8TOuflcaZEKsxw42CxonjW/2vkj1AKlinJb4ZLwBcuWF9GiPr7FQc6AQA==}
|
resolution: {integrity: sha512-ypfPFcAXHuAZRCzo3vJL6ltENzniTjwe/qsLleH1V2/7SRDjgvRQyrLmumFTLmjFax4IuSxfGXEn79fozXcJog==}
|
||||||
engines: {node: '>=18.0'}
|
engines: {node: '>=18.0'}
|
||||||
|
|
||||||
cross-spawn@7.0.3:
|
cross-spawn@7.0.3:
|
||||||
@ -3997,8 +4001,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
|
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
package-json-from-dist@1.0.0:
|
package-json-from-dist@1.0.1:
|
||||||
resolution: {integrity: sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==}
|
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
|
||||||
|
|
||||||
package-manager-detector@0.2.0:
|
package-manager-detector@0.2.0:
|
||||||
resolution: {integrity: sha512-E385OSk9qDcXhcM9LNSe4sdhx8a9mAPrZ4sMLW+tmxl5ZuGtPUcdFu+MPP2jbgiWAZ6Pfe5soGFMd+0Db5Vrog==}
|
resolution: {integrity: sha512-E385OSk9qDcXhcM9LNSe4sdhx8a9mAPrZ4sMLW+tmxl5ZuGtPUcdFu+MPP2jbgiWAZ6Pfe5soGFMd+0Db5Vrog==}
|
||||||
@ -7300,7 +7304,7 @@ snapshots:
|
|||||||
crc-32: 1.2.2
|
crc-32: 1.2.2
|
||||||
readable-stream: 4.5.2
|
readable-stream: 4.5.2
|
||||||
|
|
||||||
croner@8.1.1: {}
|
croner@8.1.2: {}
|
||||||
|
|
||||||
cross-spawn@7.0.3:
|
cross-spawn@7.0.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -8222,7 +8226,7 @@ snapshots:
|
|||||||
jackspeak: 3.4.3
|
jackspeak: 3.4.3
|
||||||
minimatch: 9.0.5
|
minimatch: 9.0.5
|
||||||
minipass: 7.1.2
|
minipass: 7.1.2
|
||||||
package-json-from-dist: 1.0.0
|
package-json-from-dist: 1.0.1
|
||||||
path-scurry: 1.11.1
|
path-scurry: 1.11.1
|
||||||
|
|
||||||
glob@7.2.3:
|
glob@7.2.3:
|
||||||
@ -8893,7 +8897,7 @@ snapshots:
|
|||||||
citty: 0.1.6
|
citty: 0.1.6
|
||||||
consola: 3.2.3
|
consola: 3.2.3
|
||||||
cookie-es: 1.2.2
|
cookie-es: 1.2.2
|
||||||
croner: 8.1.1
|
croner: 8.1.2
|
||||||
crossws: 0.2.4
|
crossws: 0.2.4
|
||||||
db0: '@ourongxing/db0@0.1.6(@libsql/client@0.14.0)(libsql@0.4.6)'
|
db0: '@ourongxing/db0@0.1.6(@libsql/client@0.14.0)(libsql@0.4.6)'
|
||||||
defu: 6.1.4
|
defu: 6.1.4
|
||||||
@ -9100,7 +9104,7 @@ snapshots:
|
|||||||
|
|
||||||
p-try@2.2.0: {}
|
p-try@2.2.0: {}
|
||||||
|
|
||||||
package-json-from-dist@1.0.0: {}
|
package-json-from-dist@1.0.1: {}
|
||||||
|
|
||||||
package-manager-detector@0.2.0: {}
|
package-manager-detector@0.2.0: {}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { Database } from "@ourongxing/db0"
|
import type { Database } from "db0"
|
||||||
import type { UserInfo } from "#/types"
|
import type { UserInfo } from "#/types"
|
||||||
|
|
||||||
export class UserTable {
|
export class UserTable {
|
||||||
@ -31,6 +31,8 @@ export class UserTable {
|
|||||||
} else if (u.email !== email && u.type !== type) {
|
} else if (u.email !== email && u.type !== type) {
|
||||||
await this.db.prepare(`REPLACE INTO user (id, email, updated) VALUES (?, ?, ?)`).run(id, email, now)
|
await this.db.prepare(`REPLACE INTO user (id, email, updated) VALUES (?, ?, ?)`).run(id, email, now)
|
||||||
logger.success(`update user ${id} email`)
|
logger.success(`update user ${id} email`)
|
||||||
|
} else {
|
||||||
|
logger.info(`user ${id} already exists`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,10 +2,13 @@ import process from "node:process"
|
|||||||
import { jwtVerify } from "jose"
|
import { jwtVerify } from "jose"
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(async (event) => {
|
||||||
const token = getCookie(event, "jwt")
|
if (["JWT_SECRET", "G_CLIENT_ID", "G_CLIENT_SECRET"].find(k => !process.env[k])) {
|
||||||
|
event.context.user = true
|
||||||
|
} else {
|
||||||
|
const token = getHeader(event, "Authorization")
|
||||||
if (token && process.env.JWT_SECRET) {
|
if (token && process.env.JWT_SECRET) {
|
||||||
try {
|
try {
|
||||||
const { payload } = await jwtVerify(token, new TextEncoder().encode(process.env.JWT_SECRET)) as { payload?: { id: string, type: string } }
|
const { payload } = await jwtVerify(token.replace("Bearer ", ""), new TextEncoder().encode(process.env.JWT_SECRET)) as { payload?: { id: string, type: string } }
|
||||||
if (payload?.id) {
|
if (payload?.id) {
|
||||||
event.context.user = payload.id
|
event.context.user = payload.id
|
||||||
}
|
}
|
||||||
@ -13,4 +16,5 @@ export default defineEventHandler(async (event) => {
|
|||||||
logger.error("JWT verification failed")
|
logger.error("JWT verification failed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,3 +1,2 @@
|
|||||||
export default defineEventHandler(async (event) => {
|
export default defineEventHandler(() => {
|
||||||
return event
|
|
||||||
})
|
})
|
||||||
|
@ -1,75 +0,0 @@
|
|||||||
import process from "node:process"
|
|
||||||
import { SignJWT } from "jose"
|
|
||||||
import { UserTable } from "#/database/user"
|
|
||||||
|
|
||||||
export default defineEventHandler(async (event) => {
|
|
||||||
["JWT_SECRET", "G_CLIENT_ID", "G_CLIENT_SECRET"].forEach((k) => {
|
|
||||||
if (!process.env[k]) throw new Error(`${k} is not defined`)
|
|
||||||
})
|
|
||||||
|
|
||||||
const db = useDatabase()
|
|
||||||
const userTable = db ? new UserTable(db) : undefined
|
|
||||||
if (!userTable) throw new Error("db is not defined")
|
|
||||||
await userTable.init()
|
|
||||||
const body = {
|
|
||||||
client_id: process.env.G_CLIENT_ID,
|
|
||||||
client_secret: process.env.G_CLIENT_SECRET,
|
|
||||||
code: getQuery(event).code,
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 获取access_token
|
|
||||||
* 接下来的操作都需要使用access_token
|
|
||||||
*/
|
|
||||||
const response: {
|
|
||||||
access_token: string
|
|
||||||
token_type: string
|
|
||||||
scope: string
|
|
||||||
} = await $fetch(
|
|
||||||
`https://github.com/login/oauth/access_token`,
|
|
||||||
{
|
|
||||||
method: "POST",
|
|
||||||
body,
|
|
||||||
headers: { accept: "application/json" },
|
|
||||||
},
|
|
||||||
)
|
|
||||||
const token = response.access_token
|
|
||||||
|
|
||||||
console.log(token)
|
|
||||||
const userInfo: {
|
|
||||||
id: number
|
|
||||||
name: string
|
|
||||||
avatar_url: string
|
|
||||||
} = await $fetch(`https://api.github.com/user`, {
|
|
||||||
headers: {
|
|
||||||
Authorization: `token ${token}`,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const emailinfo: {
|
|
||||||
email: string
|
|
||||||
primary: boolean
|
|
||||||
}[] = await $fetch("https://api.github.com/user/emails", {
|
|
||||||
headers: {
|
|
||||||
Accept: "application/vnd.github+json",
|
|
||||||
Authorization: `token ${token}`,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const userID = String(userInfo.id)
|
|
||||||
await userTable.addUser(userID, emailinfo.find(item => item.primary)?.email || "", "github")
|
|
||||||
|
|
||||||
const jwtToken = await new SignJWT({
|
|
||||||
id: userID,
|
|
||||||
type: "github",
|
|
||||||
})
|
|
||||||
.setExpirationTime("65d")
|
|
||||||
.setProtectedHeader({ alg: "HS256" })
|
|
||||||
.sign(new TextEncoder().encode(process.env.JWT_SECRET!))
|
|
||||||
|
|
||||||
// seconds
|
|
||||||
const maxAge = 60 * 24 * 60 * 60
|
|
||||||
setCookie(event, "jwt", jwtToken, { maxAge })
|
|
||||||
setCookie(event, "avatar", userInfo.avatar_url, { maxAge })
|
|
||||||
setCookie(event, "name", userInfo.name, { maxAge })
|
|
||||||
return sendRedirect(event, `/?login=github`)
|
|
||||||
})
|
|
73
server/routes/oauth/github.ts
Normal file
73
server/routes/oauth/github.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import process from "node:process"
|
||||||
|
import { SignJWT } from "jose"
|
||||||
|
import { UserTable } from "#/database/user"
|
||||||
|
|
||||||
|
export default defineEventHandler(async (event) => {
|
||||||
|
if (["JWT_SECRET", "G_CLIENT_ID", "G_CLIENT_SECRET"].find(k => !process.env[k])) throw new Error("Missing environment variables")
|
||||||
|
const db = useDatabase()
|
||||||
|
const userTable = db ? new UserTable(db) : undefined
|
||||||
|
if (!userTable) throw new Error("db is not defined")
|
||||||
|
if (process.env.INIT_TABLE !== "false") await userTable.init()
|
||||||
|
|
||||||
|
const response: {
|
||||||
|
access_token: string
|
||||||
|
token_type: string
|
||||||
|
scope: string
|
||||||
|
} = await $fetch(
|
||||||
|
`https://github.com/login/oauth/access_token`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
client_id: process.env.G_CLIENT_ID,
|
||||||
|
client_secret: process.env.G_CLIENT_SECRET,
|
||||||
|
code: getQuery(event).code,
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
accept: "application/json",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const userInfo: {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
avatar_url: string
|
||||||
|
email: string
|
||||||
|
notification_email: string
|
||||||
|
} = await $fetch(`https://api.github.com/user`, {
|
||||||
|
headers: {
|
||||||
|
"Accept": "application/vnd.github+json",
|
||||||
|
"Authorization": `token ${response.access_token}`,
|
||||||
|
// 必须有 user-agent,在 cloudflare worker 会报错
|
||||||
|
"User-Agent": "NewsNow App",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const userID = String(userInfo.id)
|
||||||
|
await userTable.addUser(userID, userInfo.notification_email || userInfo.email, "github")
|
||||||
|
|
||||||
|
const jwtToken = await new SignJWT({
|
||||||
|
id: userID,
|
||||||
|
type: "github",
|
||||||
|
})
|
||||||
|
.setExpirationTime("60d")
|
||||||
|
.setProtectedHeader({ alg: "HS256" })
|
||||||
|
.sign(new TextEncoder().encode(process.env.JWT_SECRET!))
|
||||||
|
|
||||||
|
// nitro 有 bug,在 cloudflare 里没法 set cookie
|
||||||
|
// seconds
|
||||||
|
// const maxAge = 60 * 24 * 60 * 60
|
||||||
|
// setCookie(event, "user_jwt", jwtToken, { maxAge })
|
||||||
|
// setCookie(event, "user_avatar", userInfo.avatar_url, { maxAge })
|
||||||
|
// setCookie(event, "user_name", userInfo.name, { maxAge })
|
||||||
|
|
||||||
|
const params = new URLSearchParams({
|
||||||
|
login: "github",
|
||||||
|
user_jwt: jwtToken,
|
||||||
|
user_info: JSON.stringify({
|
||||||
|
avatar: userInfo.avatar_url,
|
||||||
|
name: userInfo.name,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
return sendRedirect(event, `/?${params.toString()}`)
|
||||||
|
})
|
@ -1,3 +1,4 @@
|
|||||||
|
import process from "node:process"
|
||||||
import { TTL } from "@shared/consts"
|
import { TTL } from "@shared/consts"
|
||||||
import type { SourceID, SourceResponse } from "@shared/types"
|
import type { SourceID, SourceResponse } from "@shared/types"
|
||||||
import { sources } from "@shared/sources"
|
import { sources } from "@shared/sources"
|
||||||
@ -21,7 +22,7 @@ export default defineEventHandler(async (event): Promise<SourceResponse> => {
|
|||||||
const cacheTable = db ? new Cache(db) : undefined
|
const cacheTable = db ? new Cache(db) : undefined
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
if (cacheTable) {
|
if (cacheTable) {
|
||||||
await cacheTable.init()
|
if (process.env.INIT_TABLE !== "false") await cacheTable.init()
|
||||||
const cache = await cacheTable.get(id)
|
const cache = await cacheTable.get(id)
|
||||||
if (cache) {
|
if (cache) {
|
||||||
// interval 刷新间隔,对于缓存失效也要执行的。本质上表示本来内容更新就很慢,这个间隔内可能内容压根不会更新。
|
// interval 刷新间隔,对于缓存失效也要执行的。本质上表示本来内容更新就很慢,这个间隔内可能内容压根不会更新。
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import type { NewsItem } from "@shared/types"
|
import type { NewsItem } from "@shared/types"
|
||||||
import { load } from "cheerio"
|
import { load } from "cheerio"
|
||||||
import { $fetch } from "ofetch"
|
|
||||||
|
|
||||||
export default defineSource(async () => {
|
export default defineSource(async () => {
|
||||||
const url = "https://www.36kr.com/newsflashes"
|
const url = "https://www.36kr.com/newsflashes"
|
||||||
const response = await $fetch(url)
|
const response = await $fetch(url) as any
|
||||||
const $ = load(response)
|
const $ = load(response)
|
||||||
const news: NewsItem[] = []
|
const news: NewsItem[] = []
|
||||||
const $items = $(".newsflash-item")
|
const $items = $(".newsflash-item")
|
||||||
|
@ -105,7 +105,12 @@ function NewsCard({ id, inView, handleListeners }: NewsCardProps) {
|
|||||||
if (Date.now() - _refetchTime < 1000) {
|
if (Date.now() - _refetchTime < 1000) {
|
||||||
url = `/api/s/${_id}?latest`
|
url = `/api/s/${_id}?latest`
|
||||||
}
|
}
|
||||||
const response: SourceResponse = await ofetch(url, { timeout: 5000 })
|
const response: SourceResponse = await ofetch(url, {
|
||||||
|
timeout: 10000,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${localStorage.getItem("user_jwt")}`,
|
||||||
|
},
|
||||||
|
})
|
||||||
if (response.status === "error") {
|
if (response.status === "error") {
|
||||||
throw new Error(response.message)
|
throw new Error(response.message)
|
||||||
} else {
|
} else {
|
||||||
|
@ -4,6 +4,7 @@ import { Link } from "@tanstack/react-router"
|
|||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import { useAtom } from "jotai"
|
import { useAtom } from "jotai"
|
||||||
import { useEffect } from "react"
|
import { useEffect } from "react"
|
||||||
|
import { useTitle } from "react-use"
|
||||||
import { Dnd } from "./dnd"
|
import { Dnd } from "./dnd"
|
||||||
import { currentColumnIDAtom } from "~/atoms"
|
import { currentColumnIDAtom } from "~/atoms"
|
||||||
|
|
||||||
@ -12,7 +13,7 @@ export function Column({ id }: { id: ColumnID }) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setCurrentColumnID(id)
|
setCurrentColumnID(id)
|
||||||
}, [id, setCurrentColumnID])
|
}, [id, setCurrentColumnID])
|
||||||
|
useTitle(`NewsNow ${metadata[id].name}`)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="w-full flex justify-center">
|
<div className="w-full flex justify-center">
|
||||||
|
@ -5,7 +5,7 @@ import { useIsFetching } from "@tanstack/react-query"
|
|||||||
import clsx from "clsx"
|
import clsx from "clsx"
|
||||||
import type { SourceID } from "@shared/types"
|
import type { SourceID } from "@shared/types"
|
||||||
import { Homepage, Version } from "@shared/consts"
|
import { Homepage, Version } from "@shared/consts"
|
||||||
import { useCookie } from "react-use"
|
import { useLocalStorage } from "react-use"
|
||||||
import { useDark } from "~/hooks/useDark"
|
import { useDark } from "~/hooks/useDark"
|
||||||
import { currentColumnAtom, goToTopAtom, refetchSourcesAtom } from "~/atoms"
|
import { currentColumnAtom, goToTopAtom, refetchSourcesAtom } from "~/atoms"
|
||||||
|
|
||||||
@ -22,24 +22,26 @@ function ThemeToggle() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function LoginIn() {
|
function LoginIn() {
|
||||||
const [name] = useCookie("name")
|
// useLocalStorage 默认会自动序列化
|
||||||
const [avatar] = useCookie("avatar")
|
const [info] = useLocalStorage<{ name: string, avatar: string }>("user_info")
|
||||||
const [jwt, setJwt] = useCookie("jwt")
|
const [jwt, _setJwt] = useLocalStorage<string>("user_jwt", undefined, {
|
||||||
|
raw: true,
|
||||||
|
})
|
||||||
if (jwt) {
|
if (jwt) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn-pure"
|
className="btn-pure"
|
||||||
title={name ?? ""}
|
title={info?.name ?? ""}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setJwt("")
|
// setJwt("")
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className="h-5 w-5 rounded-full bg-cover border"
|
className="h-5 w-5 rounded-full bg-cover border"
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
backgroundImage: `url(${avatar})`,
|
backgroundImage: `url(${info?.avatar})`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -48,11 +50,9 @@ function LoginIn() {
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
type="button"
|
|
||||||
title="Login in with GitHub"
|
title="Login in with GitHub"
|
||||||
className="i-ph:sign-in-duotone btn-pure"
|
className="i-ph:sign-in-duotone btn-pure"
|
||||||
// @ts-expect-error >_<
|
href={`https://github.com/login/oauth/authorize?client_id=${__G_CLIENT_ID__}`}
|
||||||
href={`https://github.com/login/oauth/authorize?client_id=${__G_CLIENT_ID__}&scope=read:user,user:email`}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -124,7 +124,7 @@ export function Header() {
|
|||||||
<RefreshButton />
|
<RefreshButton />
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
<GithubIcon />
|
<GithubIcon />
|
||||||
<LoginIn />
|
{ __ENABLE_LOGIN__ && <LoginIn />}
|
||||||
</span>
|
</span>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -15,6 +15,13 @@ export const Route = createRootRouteWithContext<{
|
|||||||
}>()({
|
}>()({
|
||||||
component: RootComponent,
|
component: RootComponent,
|
||||||
notFoundComponent: NotFoundComponent,
|
notFoundComponent: NotFoundComponent,
|
||||||
|
beforeLoad: () => {
|
||||||
|
const query = new URLSearchParams(window.location.search)
|
||||||
|
if (query.has("login")) {
|
||||||
|
[...query.entries()].forEach(key => localStorage.setItem(key[0], key[1]))
|
||||||
|
window.history.replaceState({}, document.title, window.location.pathname)
|
||||||
|
}
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
function NotFoundComponent() {
|
function NotFoundComponent() {
|
||||||
|
@ -3,14 +3,10 @@ import { useAtomValue } from "jotai"
|
|||||||
import { useMemo } from "react"
|
import { useMemo } from "react"
|
||||||
import { localSourcesAtom } from "~/atoms"
|
import { localSourcesAtom } from "~/atoms"
|
||||||
import { Column } from "~/components/column"
|
import { Column } from "~/components/column"
|
||||||
|
import { } from "cookie-es"
|
||||||
|
|
||||||
export const Route = createFileRoute("/")({
|
export const Route = createFileRoute("/")({
|
||||||
component: IndexComponent,
|
component: IndexComponent,
|
||||||
loader: async () => {
|
|
||||||
if (window.location.search.includes("login")) {
|
|
||||||
window.history.replaceState(null, "", "/")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
function IndexComponent() {
|
function IndexComponent() {
|
||||||
|
2
src/vite-env.d.ts
vendored
2
src/vite-env.d.ts
vendored
@ -1 +1,3 @@
|
|||||||
/// <reference types="vite/client" />
|
/// <reference types="vite/client" />
|
||||||
|
declare const __G_CLIENT_ID__: string
|
||||||
|
declare const __ENABLE_LOGIN__: boolean
|
||||||
|
@ -12,12 +12,13 @@ import { projectDir } from "./shared/dir"
|
|||||||
const isCF = process.env.CF_PAGES
|
const isCF = process.env.CF_PAGES
|
||||||
|
|
||||||
dotenv.config({
|
dotenv.config({
|
||||||
path: join(projectDir, ".env.vars"),
|
path: join(projectDir, ".env.server"),
|
||||||
})
|
})
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
define: {
|
define: {
|
||||||
__G_CLIENT_ID__: `"${process.env.G_CLIENT_ID}"`,
|
__G_CLIENT_ID__: `"${process.env.G_CLIENT_ID}"`,
|
||||||
|
__ENABLE_LOGIN__: ["JWT_SECRET", "G_CLIENT_ID", "G_CLIENT_SECRET"].every(k => process.env[k]),
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
TanStackRouterVite({
|
TanStackRouterVite({
|
||||||
|
Loading…
x
Reference in New Issue
Block a user