diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 59c237b6..137c6eb1 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -16,13 +16,10 @@
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"dbaeumer.vscode-eslint",
- "esbenp.prettier-vscode",
"Vue.volar",
- "nrwl.angular-console",
- "ms-playwright.playwright",
"ms-azuretools.vscode-docker",
- "vitest.explorer",
- "Codeium.codeium"
+ "Codeium.codeium",
+ "Vercel.turbo-vsc"
]
}
}
diff --git a/.vscode/extensions.json b/.vscode/extensions.json
deleted file mode 100644
index dadd80bb..00000000
--- a/.vscode/extensions.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "recommendations": ["nrwl.angular-console", "esbenp.prettier-vscode", "ms-playwright.playwright"]
-}
diff --git a/.vscode/settings.json b/.vscode/settings.json
index a7efa1f4..6f9da09f 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,5 +1,4 @@
{
- "prettier.enable": false,
"editor.formatOnSave": false,
"editor.codeActionsOnSave": {
diff --git a/apps/website/app/components/MenuProfile.vue b/apps/website/app/components/MenuProfile.vue
index 548c6611..faa7c511 100644
--- a/apps/website/app/components/MenuProfile.vue
+++ b/apps/website/app/components/MenuProfile.vue
@@ -5,8 +5,7 @@
class="profile-avatar"
@click="handleMenuClick"
>
-
{{ user }}!
-
+
Войти
@@ -14,10 +13,11 @@
@@ -43,7 +43,6 @@ function handleMenuClick() {
}
.profile-avatar {
- padding: 0.2em;
width: 58px;
height: 58px;
background-color: var(--bronze-4);
diff --git a/apps/website/package.json b/apps/website/package.json
index 57c7ce9e..1fc24b03 100644
--- a/apps/website/package.json
+++ b/apps/website/package.json
@@ -26,7 +26,6 @@
"@twurple/eventsub-ws": "^7.1.0",
"@twurple/pubsub": "^7.1.0",
"howler": "^2.2.4",
- "jsonwebtoken": "^9.0.2",
"pixi.js": "~8.2.6",
"zod": "^3.23.8"
},
@@ -36,7 +35,6 @@
"@nuxt/devtools": "^1.3.9",
"@nuxt/kit": "^3.10.0",
"@types/howler": "^2.2.11",
- "@types/jsonwebtoken": "^9.0.6",
"@types/node": "^20.14.8",
"@vueuse/core": "^11.0.1",
"@vueuse/nuxt": "^11.0.1",
diff --git a/apps/website/public/icons/twitch/112.png b/apps/website/public/icons/twitch/112.png
new file mode 100644
index 00000000..bb5e2a75
Binary files /dev/null and b/apps/website/public/icons/twitch/112.png differ
diff --git a/apps/website/public/icons/twitch/28.png b/apps/website/public/icons/twitch/28.png
new file mode 100644
index 00000000..297f5946
Binary files /dev/null and b/apps/website/public/icons/twitch/28.png differ
diff --git a/apps/website/public/icons/twitch/56.png b/apps/website/public/icons/twitch/56.png
new file mode 100644
index 00000000..114d61bd
Binary files /dev/null and b/apps/website/public/icons/twitch/56.png differ
diff --git a/apps/website/server/api/auth/me.get.ts b/apps/website/server/api/auth/me.get.ts
index b5fa3c43..bc0f996e 100644
--- a/apps/website/server/api/auth/me.get.ts
+++ b/apps/website/server/api/auth/me.get.ts
@@ -1,28 +1,12 @@
-import jwt from 'jsonwebtoken'
-import type { WebsiteProfile } from '@chat-game/types'
+export default defineEventHandler(async (event) => {
+ const session = await getUserSession(event)
-export default defineEventHandler((event) => {
- const { public: publicEnv, jwtSecretKey } = useRuntimeConfig()
-
- const token = getCookie(event, publicEnv.cookieKey)
- if (!token) {
+ if (!session?.user) {
throw createError({
statusCode: 401,
statusMessage: 'Unauthorized',
})
}
- try {
- const { profile } = jwt.verify(token, jwtSecretKey) as { profile: WebsiteProfile }
- return profile
- } catch (error) {
- if (error instanceof jwt.TokenExpiredError) {
- deleteCookie(event, publicEnv.cookieKey, { path: '/' })
- }
- }
-
- throw createError({
- statusCode: 401,
- statusMessage: 'Unauthorized',
- })
+ return session.user
})
diff --git a/apps/website/server/api/auth/sign-out.delete.ts b/apps/website/server/api/auth/sign-out.delete.ts
deleted file mode 100644
index 558128d0..00000000
--- a/apps/website/server/api/auth/sign-out.delete.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-export default defineEventHandler((event) => {
- const { public: publicEnv } = useRuntimeConfig()
-
- const token = getCookie(event, publicEnv.cookieKey)
- if (!token) {
- throw createError({
- statusCode: 401,
- statusMessage: 'Unauthorized',
- })
- }
-
- deleteCookie(event, publicEnv.cookieKey, { path: '/' })
-
- return { ok: true }
-})
diff --git a/apps/website/server/api/auth/twitch.get.ts b/apps/website/server/api/auth/twitch.get.ts
index 53fca24c..29bbca00 100644
--- a/apps/website/server/api/auth/twitch.get.ts
+++ b/apps/website/server/api/auth/twitch.get.ts
@@ -1,19 +1,42 @@
const logger = useLogger('twitch-auth')
+interface TwitchUser {
+ id: string
+ login: string
+ display_name: string
+ type: string
+ broadcaster_type: 'affiliate' | 'partner'
+ description: string
+ profile_image_url: string
+ offline_image_url: string
+ view_count: number
+ email: string
+ created_at: Date
+}
+
export default oauthTwitchEventHandler({
config: {
emailRequired: true,
},
- async onSuccess(event, { user }) {
- logger.log(JSON.stringify(user))
+ async onSuccess(event, result: { user: TwitchUser }) {
+ logger.success(JSON.stringify(result.user))
+
+ const repository = new DBRepository()
+
+ const profileInDB = await repository.findOrCreateProfile({
+ userId: result.user.id,
+ userName: result.user.login,
+ })
await setUserSession(event, {
user: {
- id: user.id,
- twitchId: user.id,
- userName: user.userName,
+ id: profileInDB.id,
+ twitchId: profileInDB.twitchId,
+ userName: profileInDB.userName,
+ imageUrl: result.user.profile_image_url,
},
})
+
return sendRedirect(event, '/')
},
// Optional, will return a json error and 401 status code by default
diff --git a/apps/website/server/api/auth/twitch3.get.ts b/apps/website/server/api/auth/twitch3.get.ts
deleted file mode 100644
index 7e4b15b4..00000000
--- a/apps/website/server/api/auth/twitch3.get.ts
+++ /dev/null
@@ -1,71 +0,0 @@
-import jwt from 'jsonwebtoken'
-import type { TwitchAccessTokenResponse, WebsiteProfile } from '@chat-game/types'
-import { getTokenInfo } from '@twurple/auth'
-
-export default defineEventHandler(async (event) => {
- const { public: publicEnv, jwtSecretKey } = useRuntimeConfig()
- const query = getQuery(event)
-
- if (!query.code) {
- throw createError({
- statusCode: 400,
- statusMessage: 'Missing code',
- })
- }
-
- const code = query.code.toString()
- const twitchResponse = await obtainTwitchAccessToken(code)
- if (!twitchResponse?.access_token) {
- throw createError({
- statusCode: 401,
- statusMessage: 'Unauthorized',
- })
- }
-
- const tokenInfo = await getTokenInfo(twitchResponse.access_token)
- if (!tokenInfo.userId || !tokenInfo.userName) {
- throw createError({
- statusCode: 400,
- statusMessage: 'Wrong userId or userName',
- })
- }
-
- const repository = new DBRepository()
-
- const profileInDB = await repository.findOrCreateProfile({
- userId: tokenInfo.userId,
- userName: tokenInfo.userName,
- })
-
- const profile: WebsiteProfile = {
- id: profileInDB.id,
- twitchId: tokenInfo.userId,
- userName: tokenInfo.userName,
- }
-
- const token = jwt.sign({ profile }, jwtSecretKey, { expiresIn: '7d' })
-
- setCookie(event, publicEnv.cookieKey, token, {
- path: '/',
- httpOnly: true,
- })
-
- return sendRedirect(event, '/')
-})
-
-async function obtainTwitchAccessToken(code: string) {
- const { public: publicEnv, oauthTwitchClientId, oauthTwitchClientSecret } = useRuntimeConfig()
-
- try {
- const response = await fetch(
- `https://id.twitch.tv/oauth2/token?client_id=${oauthTwitchClientId}&client_secret=${oauthTwitchClientSecret}&code=${code}&grant_type=authorization_code&redirect_uri=${publicEnv.signInRedirectUrl}`,
- {
- method: 'POST',
- },
- )
- return (await response.json()) as TwitchAccessTokenResponse
- } catch (err) {
- console.error('obtainTwitchAccessToken', err)
- return null
- }
-}
diff --git a/apps/website/server/middleware/auth.ts b/apps/website/server/middleware/auth.ts
index a598dba9..2c0e3882 100644
--- a/apps/website/server/middleware/auth.ts
+++ b/apps/website/server/middleware/auth.ts
@@ -10,6 +10,10 @@ export default defineEventHandler((event) => {
return
}
+ if (event.path === '/api/_auth/session') {
+ return
+ }
+
if (!token || token !== `Bearer ${websiteBearer}`) {
return createError({
statusCode: 403,
diff --git a/apps/website/types/auth.d.ts b/apps/website/types/auth.d.ts
index 79adb971..4a7a27fb 100644
--- a/apps/website/types/auth.d.ts
+++ b/apps/website/types/auth.d.ts
@@ -1,8 +1,9 @@
declare module '#auth-utils' {
interface User {
id: string
- twitchId: number
+ twitchId: string
userName: string
+ imageUrl: string
}
interface UserSession {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 29da6a41..d2c298fc 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -65,9 +65,6 @@ importers:
howler:
specifier: ^2.2.4
version: 2.2.4
- jsonwebtoken:
- specifier: ^9.0.2
- version: 9.0.2
pixi.js:
specifier: ~8.2.6
version: 8.2.6
@@ -90,9 +87,6 @@ importers:
'@types/howler':
specifier: ^2.2.11
version: 2.2.11
- '@types/jsonwebtoken':
- specifier: ^9.0.6
- version: 9.0.6
'@types/node':
specifier: ^20.14.8
version: 20.16.1
@@ -1437,9 +1431,6 @@ packages:
'@types/json-schema@7.0.15':
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
- '@types/jsonwebtoken@9.0.6':
- resolution: {integrity: sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw==}
-
'@types/mdast@3.0.15':
resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==}
@@ -1916,9 +1907,6 @@ packages:
resolution: {integrity: sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==}
engines: {node: '>=8.0.0'}
- buffer-equal-constant-time@1.0.1:
- resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
-
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
@@ -2439,9 +2427,6 @@ packages:
eastasianwidth@0.2.0:
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
- ecdsa-sig-formatter@1.0.11:
- resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
-
ee-first@1.1.1:
resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==}
@@ -3311,16 +3296,6 @@ packages:
resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==}
engines: {'0': node >= 0.2.0}
- jsonwebtoken@9.0.2:
- resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==}
- engines: {node: '>=12', npm: '>=6'}
-
- jwa@1.4.1:
- resolution: {integrity: sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==}
-
- jws@3.2.2:
- resolution: {integrity: sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==}
-
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
@@ -3396,27 +3371,12 @@ packages:
lodash.defaults@4.2.0:
resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
- lodash.includes@4.3.0:
- resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==}
-
lodash.isarguments@3.1.0:
resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==}
- lodash.isboolean@3.0.3:
- resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==}
-
- lodash.isinteger@4.0.4:
- resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==}
-
- lodash.isnumber@3.0.3:
- resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==}
-
lodash.isplainobject@4.0.6:
resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==}
- lodash.isstring@4.0.1:
- resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==}
-
lodash.kebabcase@4.1.1:
resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==}
@@ -3429,9 +3389,6 @@ packages:
lodash.mergewith@4.6.2:
resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==}
- lodash.once@4.1.1:
- resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==}
-
lodash.snakecase@4.1.1:
resolution: {integrity: sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==}
@@ -6630,10 +6587,6 @@ snapshots:
'@types/json-schema@7.0.15': {}
- '@types/jsonwebtoken@9.0.6':
- dependencies:
- '@types/node': 20.16.1
-
'@types/mdast@3.0.15':
dependencies:
'@types/unist': 2.0.11
@@ -7271,8 +7224,6 @@ snapshots:
buffer-crc32@1.0.0: {}
- buffer-equal-constant-time@1.0.1: {}
-
buffer-from@1.1.2: {}
buffer@6.0.3:
@@ -7782,10 +7733,6 @@ snapshots:
eastasianwidth@0.2.0: {}
- ecdsa-sig-formatter@1.0.11:
- dependencies:
- safe-buffer: 5.2.1
-
ee-first@1.1.1: {}
electron-to-chromium@1.5.13: {}
@@ -8818,30 +8765,6 @@ snapshots:
jsonparse@1.3.1: {}
- jsonwebtoken@9.0.2:
- dependencies:
- jws: 3.2.2
- lodash.includes: 4.3.0
- lodash.isboolean: 3.0.3
- lodash.isinteger: 4.0.4
- lodash.isnumber: 3.0.3
- lodash.isplainobject: 4.0.6
- lodash.isstring: 4.0.1
- lodash.once: 4.1.1
- ms: 2.1.3
- semver: 7.6.3
-
- jwa@1.4.1:
- dependencies:
- buffer-equal-constant-time: 1.0.1
- ecdsa-sig-formatter: 1.0.11
- safe-buffer: 5.2.1
-
- jws@3.2.2:
- dependencies:
- jwa: 1.4.1
- safe-buffer: 5.2.1
-
keyv@4.5.4:
dependencies:
json-buffer: 3.0.1
@@ -8955,20 +8878,10 @@ snapshots:
lodash.defaults@4.2.0: {}
- lodash.includes@4.3.0: {}
-
lodash.isarguments@3.1.0: {}
- lodash.isboolean@3.0.3: {}
-
- lodash.isinteger@4.0.4: {}
-
- lodash.isnumber@3.0.3: {}
-
lodash.isplainobject@4.0.6: {}
- lodash.isstring@4.0.1: {}
-
lodash.kebabcase@4.1.1: {}
lodash.memoize@4.1.2: {}
@@ -8977,8 +8890,6 @@ snapshots:
lodash.mergewith@4.6.2: {}
- lodash.once@4.1.1: {}
-
lodash.snakecase@4.1.1: {}
lodash.startcase@4.4.0: {}