-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
10 changed files
with
558 additions
and
29 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import axios from "axios" | ||
import cookies from "js-cookie" | ||
|
||
export { getAuthToken, redirectToAuth, onAuthDone } | ||
|
||
const GITHUB_CLIID = "dc5a44e3614c1250afa9" | ||
|
||
const GITHUB_AUTH_URL = "https://github.com/login/oauth/authorize" | ||
const GITHUB_AUTH_TOKEN_URL = "https://api.crashmc.com/api/v1/gh_access_token/" | ||
const GH_OAUTH_STATE_NAME = "_github_oauth_state" | ||
const GH_OAUTH_TOKEN_NAME = "_github_oauth_token" | ||
|
||
interface StateI { | ||
randstr: string | ||
redirect: string | ||
} | ||
|
||
function getAuthToken(): string | undefined { | ||
return cookies.get(GH_OAUTH_TOKEN_NAME) | ||
} | ||
|
||
function redirectToAuth(afterAuth: string | URL, scope?: string) { | ||
const randstr = btoa( | ||
String.fromCodePoint(...crypto.getRandomValues(new Uint8Array(63))), | ||
) | ||
cookies.set( | ||
GH_OAUTH_STATE_NAME, | ||
JSON.stringify({ | ||
randstr: randstr, | ||
redirect: afterAuth.toString(), | ||
} as StateI), | ||
{ | ||
secure: true, | ||
expires: 10 / (60 * 24), // in days | ||
sameSite: "strict", | ||
}, | ||
) | ||
const authURL = new URL(GITHUB_AUTH_URL) | ||
authURL.searchParams.set("client_id", GITHUB_CLIID) | ||
authURL.searchParams.set("state", randstr) | ||
authURL.searchParams.set( | ||
"redirect_uri", | ||
new URL("/_auth_redirect.html", window.location.toString()).toString(), | ||
) | ||
if (scope) { | ||
authURL.searchParams.set("scope", scope) | ||
} | ||
window.location.assign(authURL.toString()) | ||
} | ||
|
||
async function onAuthDone(): Promise<string | null> { | ||
const location = new URL(window.location.toString()) | ||
const code = location.searchParams.get("code") | ||
const randstr = location.searchParams.get("state") | ||
if (!code || !randstr) { | ||
return null | ||
} | ||
var state: StateI | ||
try { | ||
state = JSON.parse(cookies.get(GH_OAUTH_STATE_NAME)) | ||
} catch (err) { | ||
console.debug("Could not parse cookie", GH_OAUTH_STATE_NAME, err) | ||
return null | ||
} | ||
if (state.randstr !== randstr) { | ||
console.debug("Auth random state string not same, probably XSS attack") | ||
return null | ||
} | ||
var token: string | ||
try { | ||
const resp = await axios.post<string>( | ||
GITHUB_AUTH_TOKEN_URL, | ||
"code=" + escape(code), | ||
) | ||
const data = new URLSearchParams(resp.data) | ||
token = data.get("access_token") | ||
} catch (err) { | ||
console.error("auth failed:", err) | ||
return | ||
} | ||
cookies.set(GH_OAUTH_TOKEN_NAME, token, { | ||
secure: true, | ||
expires: 1, | ||
}) | ||
return state.redirect | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
<script setup lang="ts"> | ||
import { ref, onMounted } from "vue" | ||
import { onAuthDone } from "../../auth/github" | ||
const REDIRECT_TIMEOUT_SEC = 3 | ||
const loading = ref(true) | ||
const failed = ref(false) | ||
const redirectTarget = ref(null) | ||
const redirectLeft = ref(REDIRECT_TIMEOUT_SEC) | ||
function startRedirectInterval(): void { | ||
const intId = setInterval(() => { | ||
const left = (redirectLeft.value -= 1) | ||
if (left <= 0) { | ||
clearInterval(intId) | ||
window.location.replace(redirectTarget.value) | ||
} | ||
}, 1000) | ||
} | ||
onMounted(async () => { | ||
let redirectTo = await onAuthDone() | ||
if (!redirectTo) { | ||
redirectTo = "/" | ||
failed.value = true | ||
} | ||
loading.value = false | ||
redirectTarget.value = redirectTo | ||
startRedirectInterval() | ||
}) | ||
</script> | ||
|
||
<template> | ||
<div class="box"> | ||
<div v-if="loading">登录中, 请稍后</div> | ||
<div v-else-if="redirectLeft > 0"> | ||
<div v-if="failed" class="failed">登录失败.</div> | ||
<div v-else class="success">登录成功!</div> | ||
{{ redirectLeft }} 秒后跳转<span v-if="failed">到主页</span> | ||
<br /> | ||
没有跳转? 点击<a :href="redirectTarget">这里</a> | ||
</div> | ||
<a href="/">返回主页</a> | ||
</div> | ||
</template> | ||
|
||
<style scoped> | ||
.box { | ||
text-align: center; | ||
} | ||
.failed { | ||
font-size: 1.2rem; | ||
color: red; | ||
} | ||
.success { | ||
font-size: 1.2rem; | ||
color: green; | ||
} | ||
</style> |
Oops, something went wrong.