diff --git a/src/app/components/Icons/Brands/GitHub.vue b/src/app/components/Icons/Brands/GitHub.vue
new file mode 100644
index 00000000..4c90a044
--- /dev/null
+++ b/src/app/components/Icons/Brands/GitHub.vue
@@ -0,0 +1,32 @@
+
+
+
+
diff --git a/src/app/components/Icons/Google.vue b/src/app/components/Icons/Brands/Google.vue
similarity index 100%
rename from src/app/components/Icons/Google.vue
rename to src/app/components/Icons/Brands/Google.vue
diff --git a/src/app/pages/login.vue b/src/app/pages/login.vue
index c701a068..387943c7 100644
--- a/src/app/pages/login.vue
+++ b/src/app/pages/login.vue
@@ -19,8 +19,17 @@
href="/api/auth/google"
class="flex cursor-pointer items-center justify-center gap-2 rounded border border-gray-300 bg-white py-2 text-sm text-gray-700 shadow-sm transition hover:bg-gray-50 dark:border-neutral-600 dark:bg-neutral-800 dark:text-neutral-200 dark:hover:bg-neutral-700"
>
-
- {{ $t('login.signInWithGoogle') }}
+
+ {{ $t('login.signInWith', ['Google']) }}
+
+
+
+
+ {{ $t('login.signInWith', ['GitHub']) }}
diff --git a/src/i18n/locales/en.json b/src/i18n/locales/en.json
index 4c065256..b8f2bc6e 100644
--- a/src/i18n/locales/en.json
+++ b/src/i18n/locales/en.json
@@ -72,7 +72,7 @@
},
"login": {
"signIn": "Sign In",
- "signInWithGoogle": "Sign in with Google",
+ "signInWith": "Sign In with {0}",
"or": "or",
"rememberMe": "Remember me",
"rememberMeDesc": "Stay logged after closing the browser",
diff --git a/src/i18n/locales/pl.json b/src/i18n/locales/pl.json
index ebc989c3..be500131 100644
--- a/src/i18n/locales/pl.json
+++ b/src/i18n/locales/pl.json
@@ -72,7 +72,6 @@
},
"login": {
"signIn": "Zaloguj się",
- "signInWithGoogle": "Zaloguj się przez Google",
"or": "lub",
"rememberMe": "Zapamiętaj mnie",
"rememberMeDesc": "Pozostań zalogowany po zamknięciu przeglądarki",
diff --git a/src/server/api/auth/[provider]/callback.get.ts b/src/server/api/auth/[provider]/callback.get.ts
index 5e2a5fe4..b1fd7105 100644
--- a/src/server/api/auth/[provider]/callback.get.ts
+++ b/src/server/api/auth/[provider]/callback.get.ts
@@ -1,7 +1,7 @@
import * as client from 'openid-client';
export default defineEventHandler(async (event) => {
- const { config, provider } = await buildOauthConfig(event);
+ const { config, provider, providerConfig } = await buildOauthConfig(event);
const session = await useWGSession(event);
if (
@@ -19,12 +19,18 @@ export default defineEventHandler(async (event) => {
const tokens = await client.authorizationCodeGrant(config, currentUrl, {
pkceCodeVerifier: session.data.oauth_verifier,
- expectedNonce: session.data.oauth_nonce,
+ expectedNonce:
+ providerConfig.isOIDC === false ? undefined : session.data.oauth_nonce,
expectedState: session.data.oauth_state,
- idTokenExpected: true,
+ idTokenExpected: providerConfig.isOIDC,
});
- const subject = tokens.claims()?.sub;
+ type SubjectType = string | undefined | typeof client.skipSubjectCheck;
+ let subject: SubjectType = tokens.claims()?.sub;
+ if (providerConfig.isOIDC === false) {
+ subject = client.skipSubjectCheck;
+ }
+
if (!subject) {
throw createError({
statusCode: 400,
@@ -32,11 +38,21 @@ export default defineEventHandler(async (event) => {
});
}
- const userInfo = await client.fetchUserInfo(
- config,
- tokens.access_token,
- subject
- );
+ let userInfo;
+ if (providerConfig.userInfoFlow === 'github') {
+ userInfo = await githubUserInfoFlow(tokens.access_token);
+ } else {
+ userInfo = await client.fetchUserInfo(config, tokens.access_token, subject);
+ }
+
+ console.log(userInfo);
+
+ if (!userInfo.sub) {
+ throw createError({
+ statusCode: 400,
+ statusMessage: 'No sub set',
+ });
+ }
if (!userInfo.email) {
throw createError({
@@ -55,8 +71,9 @@ export default defineEventHandler(async (event) => {
const result = await Database.users.findOrCreateByProvider(
provider,
userInfo.sub,
+ userInfo.preferred_username || userInfo.email,
userInfo.email,
- userInfo.preferred_username || userInfo.name || userInfo.email
+ userInfo.name || 'User'
);
if (!result.success) {
diff --git a/src/server/database/repositories/user/service.ts b/src/server/database/repositories/user/service.ts
index ad7e3a6c..37ea9bcc 100644
--- a/src/server/database/repositories/user/service.ts
+++ b/src/server/database/repositories/user/service.ts
@@ -108,6 +108,7 @@ export class UserService {
async findOrCreateByProvider(
provider: OAUTH_PROVIDER,
oauthId: string,
+ username: string,
email: string,
name: string
) {
@@ -142,7 +143,7 @@ export class UserService {
// Create new user
await this.#db.insert(user).values({
- username: email,
+ username,
password: '--- no password ---',
email,
name,
diff --git a/src/server/utils/oauth.ts b/src/server/utils/oauth.ts
index 1be1220d..a22ab51e 100644
--- a/src/server/utils/oauth.ts
+++ b/src/server/utils/oauth.ts
@@ -1,17 +1,42 @@
import type { H3Event } from 'h3';
import { discovery } from 'openid-client';
-export const OAUTH_PROVIDERS = {
- google: {
- server: 'https://accounts.google.com',
- scope: 'openid email profile',
- clientId: process.env.OAUTH_GOOGLE_CLIENT_ID,
- clientSecret: process.env.OAUTH_GOOGLE_CLIENT_SECRET,
- params: {
- access_type: 'online',
- prompt: 'select_account',
- },
+type OAuthConfig = {
+ server: string;
+ scope: string;
+ clientId: string | undefined;
+ clientSecret: string | undefined;
+ params: Record;
+ isOIDC?: false;
+ userInfoFlow?: 'github';
+};
+
+const GoogleConfig: OAuthConfig = {
+ server: 'https://accounts.google.com',
+ scope: 'openid email profile',
+ clientId: process.env.OAUTH_GOOGLE_CLIENT_ID,
+ clientSecret: process.env.OAUTH_GOOGLE_CLIENT_SECRET,
+ params: {
+ access_type: 'online',
+ prompt: 'select_account',
+ },
+};
+const GithubConfig: OAuthConfig = {
+ server: 'https://github.com/login/oauth',
+ scope: 'read:user user:email',
+ clientId: process.env.OAUTH_GITHUB_CLIENT_ID,
+ clientSecret: process.env.OAUTH_GITHUB_CLIENT_SECRET,
+ params: {
+ allow_signup: 'false',
+ prompt: 'select_account',
},
+ isOIDC: false,
+ userInfoFlow: 'github',
+};
+
+export const OAUTH_PROVIDERS = {
+ google: GoogleConfig,
+ github: GithubConfig,
};
export type OAUTH_PROVIDER = keyof typeof OAUTH_PROVIDERS;
@@ -65,3 +90,53 @@ export async function buildOauthConfig(event: H3Event) {
return { config, providerConfig: oauthProvider, provider };
}
+
+export async function githubUserInfoFlow(accessToken: string) {
+ const OAUTH_GITHUB_FLOW = {
+ userinfo_endpoint: 'https://api.github.com/user',
+ email_endpoint: 'https://api.github.com/user/emails',
+ };
+ type OAUTH_GITHUB_USERINFO = {
+ id: number;
+ login: string;
+ avatar_url: string;
+ email: string | null;
+ name: string | null;
+ };
+ type OAUTH_GITHUB_EMAIL = {
+ email: string;
+ primary: boolean;
+ verified: boolean;
+ visibility: string | null;
+ }[];
+
+ const response = await $fetch(
+ OAUTH_GITHUB_FLOW.userinfo_endpoint,
+ {
+ headers: {
+ 'User-Agent': 'wg-easy',
+ Authorization: `Bearer ${accessToken}`,
+ },
+ }
+ );
+ if (!response.email) {
+ const emailResponse = await $fetch(
+ OAUTH_GITHUB_FLOW.email_endpoint,
+ {
+ headers: {
+ 'User-Agent': 'wg-easy',
+ Authorization: `Bearer ${accessToken}`,
+ },
+ }
+ );
+ const primaryEmail = emailResponse.find((v) => v.primary && v.verified);
+ response.email = primaryEmail?.email || null;
+ }
+ return {
+ sub: response.id.toString(),
+ email: response.email,
+ email_verified: true,
+ preferred_username: response.login,
+ name: response.name || response.login,
+ };
+}