Skip to content

Commit

Permalink
feat: working smtp oauth gmail
Browse files Browse the repository at this point in the history
  • Loading branch information
potts99 committed Oct 21, 2024
1 parent 070d9af commit 43011d5
Show file tree
Hide file tree
Showing 8 changed files with 232 additions and 18 deletions.
71 changes: 70 additions & 1 deletion apps/api/src/controllers/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
// SSO Provider
// Portal Locale
// Feature Flags

import { GoogleAuth, OAuth2Client } from "google-auth-library";
import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
import nodeMailer from "nodemailer";

Expand Down Expand Up @@ -249,6 +249,10 @@ export function configRoutes(fastify: FastifyInstance) {
reply: replyto,
username,
password,
serviceType,
clientId,
clientSecret,
redirectUri,
}: any = request.body;

const email = await prisma.email.findFirst();
Expand All @@ -262,6 +266,8 @@ export function configRoutes(fastify: FastifyInstance) {
user: username,
pass: password,
active: true,
clientId: clientId,
clientSecret: clientSecret,
},
});
} else {
Expand All @@ -274,10 +280,73 @@ export function configRoutes(fastify: FastifyInstance) {
user: username,
pass: password,
active: active,
clientId: clientId,
clientSecret: clientSecret,
},
});
}

if (serviceType === "gmail") {
const email = await prisma.email.findFirst();

const google = new OAuth2Client(
//@ts-expect-error
email?.clientId,
email?.clientSecret,
"http://localhost:3000/admin/smtp/oauth"
);

const authorizeUrl = google.generateAuthUrl({
access_type: "offline",
scope: "https://www.googleapis.com/auth/gmail.send",
});

reply.send({
success: true,
message: "SSO Provider updated!",
authorizeUrl: authorizeUrl,
});
}

reply.send({
success: true,
message: "SSO Provider updated!",
});
}
}
);

fastify.get(
"/api/v1/config/email/oauth/gmail",

async (request: FastifyRequest, reply: FastifyReply) => {
const bearer = request.headers.authorization!.split(" ")[1];
const token = checkToken(bearer);

if (token) {
const { code }: any = request.query;

console.log(code);

const email = await prisma.email.findFirst();

const google = new OAuth2Client(
//@ts-expect-error
email?.clientId,
email?.clientSecret,
"http://localhost:3000/admin/smtp/oauth"
);

const r = await google.getToken(code);
// Make sure to set the credentials on the OAuth2 client.

await prisma.email.update({
where: { id: email?.id },
data: {
refreshToken: r.tokens.refresh_token,
},
});

reply.send({
success: true,
message: "SSO Provider updated!",
Expand Down
11 changes: 2 additions & 9 deletions apps/api/src/lib/nodemailer/transport.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { prisma } from "../../prisma";

const nodemailer = require("nodemailer");
const { google } = require("google-auth-library");
const { ConfidentialClientApplication } = require("@azure/identity");

export async function createTransportProvider() {
Expand All @@ -19,13 +18,7 @@ export async function createTransportProvider() {
// OAuth2 configuration
if (provider?.serviceType === "gmail") {
// Gmail
const oAuth2Client = new google.auth.OAuth2(
provider?.clientId,
provider?.clientSecret
);
oAuth2Client.setCredentials({ refresh_token: provider?.refreshToken });
const accessToken = await oAuth2Client.getAccessToken();


return nodemailer.createTransport({
service: "gmail",
auth: {
Expand All @@ -34,7 +27,7 @@ export async function createTransportProvider() {
clientId: provider?.clientId,
clientSecret: provider?.clientSecret,
refreshToken: provider?.refreshToken,
accessToken: accessToken.token,
// accessToken: accessToken.token,
},
});
} else if (provider?.serviceType === "microsoft") {
Expand Down
1 change: 1 addition & 0 deletions apps/client/@/shadcn/ui/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//@ts-nocheck
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { VariantProps, cva } from "class-variance-authority"
Expand Down
4 changes: 2 additions & 2 deletions apps/client/layouts/adminLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ export default function AdminLayout({ children }: any) {
},
{
name: "SMTP Email",
href: "/admin/email",
current: location.pathname === "/admin/email",
href: "/admin/smtp",
current: location.pathname === "/admin/smtp",
icon: Mailbox,
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
import { ExclamationTriangleIcon } from "@heroicons/react/20/solid";
import { notifications } from "@mantine/notifications";
import { getCookie } from "cookies-next";
import { useRouter } from "next/router";
import { useEffect, useState } from "react";

export default function Notifications() {
Expand Down Expand Up @@ -169,9 +170,7 @@ export default function Notifications() {
<SelectItem disabled value="microsoft">
Microsoft
</SelectItem>
<SelectItem disabled value="gmail">
Google
</SelectItem>
<SelectItem value="gmail">Google</SelectItem>
<SelectItem value="other">Other</SelectItem>
</SelectContent>
</Select>
Expand All @@ -192,7 +191,9 @@ export default function Notifications() {
{step === 1 && provider === "microsoft" && (
<MicrosoftSettings />
)}
{step === 1 && provider === "gmail" && <GmailSettings />}
{step === 1 && provider === "gmail" && (
<GmailSettings setStep={setStep} />
)}
{step === 1 && provider === "other" && (
<SMTP setStep={setStep} />
)}
Expand All @@ -211,8 +212,124 @@ function MicrosoftSettings() {
return <div>Microsoft</div>;
}

function GmailSettings() {
return <div>Gmail</div>;
function GmailSettings({ setStep }: { setStep: (step: number) => void }) {
const [clientId, setClientId] = useState("");
const [clientSecret, setClientSecret] = useState("");
const [refreshToken, setRefreshToken] = useState("");
const [user, setUser] = useState("");

const router = useRouter();

async function submitGmailConfig() {
await fetch(`/api/v1/config/email`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${getCookie("session")}`,
},
body: JSON.stringify({
host: "smtp.gmail.com",
port: "465",
clientId,
clientSecret,
username: user,
reply: user,
serviceType: "gmail",
}),
})
.then((res) => res.json())
.then((res) => {
if (res.success && res.authorizeUrl) {
router.push(res.authorizeUrl);
}
});
}

return (
<Card className="w-[350px]">
<CardHeader>
<CardTitle>Gmail Settings</CardTitle>
<CardDescription>Configure your Gmail OAuth2 settings.</CardDescription>
</CardHeader>
<CardContent>
<div className="grid w-full items-center gap-4">
<div className="flex flex-col space-y-4">
<div className="">
<label
htmlFor="client_id"
className="block text-sm font-medium text-foreground"
>
Client ID
</label>
<div className="mt-1 flex rounded-md shadow-sm">
<input
type="text"
name="client_id"
id="client_id"
className="flex-1 text-foreground text-sm bg-transparent focus:ring-green-500 focus:border-green-500 block w-full min-w-0 rounded-md"
placeholder="Your Client ID"
value={clientId}
onChange={(e) => setClientId(e.target.value)}
/>
</div>
</div>

<div className="">
<label
htmlFor="client_secret"
className="block text-sm font-medium text-foreground"
>
Client Secret
</label>
<div className="mt-1 flex rounded-md shadow-sm">
<input
type="text"
name="client_secret"
id="client_secret"
className="flex-1 text-foreground text-sm bg-transparent focus:ring-green-500 focus:border-green-500 block w-full min-w-0 rounded-md"
placeholder="Your Client Secret"
value={clientSecret}
onChange={(e) => setClientSecret(e.target.value)}
/>
</div>
</div>

<div className="">
<label
htmlFor="user_email"
className="block text-sm font-medium text-foreground"
>
User Email
</label>
<div className="mt-1 flex rounded-md shadow-sm">
<input
type="email"
name="user_email"
id="user_email"
className="flex-1 text-foreground text-sm bg-transparent focus:ring-green-500 focus:border-green-500 block w-full min-w-0 rounded-md"
placeholder="Your Email"
value={user}
onChange={(e) => setUser(e.target.value)}
/>
</div>
</div>
</div>
</div>
</CardContent>
<CardFooter className="flex justify-between">
<Button size="sm" variant="outline" onClick={() => setStep(0)}>
Back
</Button>
<Button
size="sm"
disabled={!clientId || !clientSecret || !user}
onClick={() => submitGmailConfig()}
>
Submit
</Button>
</CardFooter>
</Card>
);
}

function SMTP({ setStep }: { setStep: (step: number) => void }) {
Expand Down
34 changes: 34 additions & 0 deletions apps/client/pages/admin/smtp/oauth.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { getCookie, setCookie } from "cookies-next";
import { useRouter } from "next/router";
import { useEffect } from "react";
import { useUser } from "../../../store/session";

export default function Login() {
const router = useRouter();

async function check() {
if (router.query.code) {
await fetch(
`/api/v1/config/email/oauth/gmail?code=${router.query.code}`,
{
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${getCookie("session")}`,
},
}
)
.then((res) => res.json())
.then((res) => {
if (res.success) {
router.push("/admin/smtp");
}
});
}
}

useEffect(() => {
check();
}, [router]);

return <div></div>;
}

0 comments on commit 43011d5

Please sign in to comment.