feat(social-login): social login with Google is working (#7218)

Co-authored-by: Víctor Fernández Poyatos <victor@prowler.com>
This commit is contained in:
Pablo Lara
2025-03-13 12:52:30 +01:00
committed by Pepe Fagoaga
parent 3cd4e4cbbf
commit 412697c418
7 changed files with 221 additions and 5 deletions
+4
View File
@@ -30,6 +30,10 @@ class ProwlerSocialAccountAdapter(DefaultSocialAccountAdapter):
with transaction.atomic(using=MainRouter.admin_db):
user = super().save_user(request, sociallogin, form)
user.save(using=MainRouter.admin_db)
social_account_name = sociallogin.account.extra_data.get("name")
if social_account_name:
user.name = social_account_name
user.save(using=MainRouter.admin_db)
tenant = Tenant.objects.using(MainRouter.admin_db).create(
name=f"{user.email.split('@')[0]} default tenant"
+68
View File
@@ -0,0 +1,68 @@
"use server";
import { NextResponse } from "next/server";
import { signIn } from "@/auth.config";
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const keyServer = process.env.API_BASE_URL;
const code = searchParams.get("code");
const params = new URLSearchParams();
params.append("code", code || "");
if (!code) {
return NextResponse.json(
{ error: "Authorization code is missing" },
{ status: 400 },
);
}
try {
const response = await fetch(`${keyServer}/tokens/github`, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: params.toString(),
});
if (!response.ok) {
throw new Error("Failed to exchange code for tokens");
}
const data = await response.json();
const { access, refresh } = data.data.attributes;
try {
const result = await signIn("social-oauth", {
accessToken: access,
refreshToken: refresh,
redirect: false,
callbackUrl: "/",
});
if (result?.error) {
throw new Error(result.error);
}
return NextResponse.redirect(new URL("/", req.url));
} catch (error) {
// eslint-disable-next-line no-console
console.error("SignIn error:", error);
return NextResponse.redirect(
new URL("/sign-in?error=AuthenticationFailed", req.url),
);
}
} catch (error) {
// eslint-disable-next-line no-console
console.error("Error in Github callback:", error);
return NextResponse.json(
{ error: (error as Error).message },
{ status: 500 },
);
}
}
+68
View File
@@ -0,0 +1,68 @@
"use server";
import { NextResponse } from "next/server";
import { signIn } from "@/auth.config";
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const keyServer = process.env.API_BASE_URL;
const code = searchParams.get("code");
const params = new URLSearchParams();
params.append("code", code || "");
if (!code) {
return NextResponse.json(
{ error: "Authorization code is missing" },
{ status: 400 },
);
}
try {
const response = await fetch(`${keyServer}/tokens/google`, {
method: "POST",
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
body: params.toString(),
});
if (!response.ok) {
throw new Error("Failed to exchange code for tokens");
}
const data = await response.json();
const { access, refresh } = data.data.attributes;
try {
const result = await signIn("social-oauth", {
accessToken: access,
refreshToken: refresh,
redirect: false,
callbackUrl: "/",
});
if (result?.error) {
throw new Error(result.error);
}
return NextResponse.redirect(new URL("/", req.url));
} catch (error) {
// eslint-disable-next-line no-console
console.error("SignIn error:", error);
return NextResponse.redirect(
new URL("/sign-in?error=AuthenticationFailed", req.url),
);
}
} catch (error) {
// eslint-disable-next-line no-console
console.error("Error in Google callback:", error);
return NextResponse.json(
{ error: (error as Error).message },
{ status: 500 },
);
}
}
+36 -1
View File
@@ -99,13 +99,48 @@ export const authConfig = {
};
},
}),
Credentials({
id: "social-oauth",
name: "social-oauth",
credentials: {
accessToken: { label: "Access Token", type: "text" },
refreshToken: { label: "Refresh Token", type: "text" },
},
async authorize(credentials) {
const accessToken = credentials?.accessToken;
if (!accessToken) {
return null;
}
try {
const userMeResponse = await getUserByMe(accessToken as string);
const user = {
name: userMeResponse.name,
email: userMeResponse.email,
company: userMeResponse?.company,
dateJoined: userMeResponse.dateJoined,
};
return {
...user,
accessToken: credentials.accessToken,
refreshToken: credentials.refreshToken,
};
} catch (error) {
// eslint-disable-next-line no-console
console.error("Error in authorize:", error);
return null;
}
},
}),
],
callbacks: {
authorized({ auth, request: { nextUrl } }) {
const isLoggedIn = !!auth?.user;
const isOnDashboard = nextUrl.pathname.startsWith("/");
const isSignUpPage = nextUrl.pathname === "/sign-up";
//CLOUD API CHANGES
// Allow access to sign-up page
if (isSignUpPage) return true;
+9 -3
View File
@@ -1,7 +1,8 @@
"use client";
import { zodResolver } from "@hookform/resolvers/zod";
import { Checkbox, Link } from "@nextui-org/react";
import { Icon } from "@iconify/react";
import { Button, Checkbox, Divider, Link } from "@nextui-org/react";
import { useRouter } from "next/navigation";
import { useForm } from "react-hook-form";
import { z } from "zod";
@@ -17,6 +18,7 @@ import {
FormField,
FormMessage,
} from "@/components/ui/form";
import { getAuthUrl } from "@/lib/helper";
import { ApiError, authFormSchema } from "@/types";
export const AuthForm = ({
@@ -285,7 +287,7 @@ export const AuthForm = ({
</form>
</Form>
{/* {type === "sign-in" && (
{type === "sign-in" && (
<>
<div className="flex items-center gap-4 py-2">
<Divider className="flex-1" />
@@ -298,6 +300,8 @@ export const AuthForm = ({
<Icon icon="flat-color-icons:google" width={24} />
}
variant="bordered"
as="a"
href={getAuthUrl("google")}
>
Continue with Google
</Button>
@@ -310,12 +314,14 @@ export const AuthForm = ({
/>
}
variant="bordered"
as="a"
href={getAuthUrl("github")}
>
Continue with Github
</Button>
</div>
</>
)} */}
)}
{type === "sign-in" ? (
<p className="text-center text-small">
Need to create an account?&nbsp;
+34 -1
View File
@@ -1,5 +1,38 @@
import { getTask } from "@/actions/task";
import { MetaDataProps, PermissionInfo } from "@/types";
import { AuthSocialProvider, MetaDataProps, PermissionInfo } from "@/types";
export const getAuthUrl = (provider: AuthSocialProvider) => {
const config = {
google: {
baseUrl: "https://accounts.google.com/o/oauth2/v2/auth",
params: {
redirect_uri: process.env.NEXT_PUBLIC_GOOGLE_CALLBACK_URI,
prompt: "consent",
response_type: "code",
client_id: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID,
scope: "openid email profile",
access_type: "offline",
},
},
github: {
baseUrl: "https://github.com/login/oauth/authorize",
params: {
client_id: process.env.NEXT_PUBLIC_GITHUB_OAUTH_CLIENT_ID,
redirect_uri: process.env.NEXT_PUBLIC_GITHUB_OAUTH_CALLBACK_URL,
scope: "user:email",
},
},
};
const { baseUrl, params } = config[provider];
const url = new URL(baseUrl);
Object.entries(params).forEach(([key, value]) => {
url.searchParams.set(key, value || "");
});
return url.toString();
};
export async function checkTaskStatus(
taskId: string,
+2
View File
@@ -1,5 +1,7 @@
import { z } from "zod";
export type AuthSocialProvider = "google" | "github";
export const authFormSchema = (type: string) =>
z
.object({