mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-19 10:31:51 +00:00
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:
@@ -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"
|
||||
|
||||
@@ -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 },
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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?
|
||||
|
||||
+34
-1
@@ -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,
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export type AuthSocialProvider = "google" | "github";
|
||||
|
||||
export const authFormSchema = (type: string) =>
|
||||
z
|
||||
.object({
|
||||
|
||||
Reference in New Issue
Block a user