From 06be23b13dd9acc206dffdaed8bcd4d6b609e7fc Mon Sep 17 00:00:00 2001 From: Aditya Date: Tue, 19 Nov 2024 16:46:20 +0530 Subject: [PATCH 1/2] full appwrite-auth --- .env.sample | 7 ++- app/api/auth/login/route.ts | 4 +- app/api/auth/logout/route.ts | 13 +++++ app/api/auth/oauth/github/login/route.ts | 5 +- .../auth/oauth/github/login/success/route.ts | 3 +- app/api/auth/oauth/github/register/route.ts | 5 +- .../oauth/github/register/success/route.ts | 3 +- app/api/auth/register/route.ts | 20 ++++--- app/api/user/email/verify/resend/route.ts | 3 +- .../user/recovery/password/confirm/route.ts | 55 +++++++++++++++++++ app/api/user/recovery/password/route.ts | 34 ++++++++++++ app/api/user/sessions/route.ts | 13 +++++ helpers/sessionCookieFunctions.ts | 8 +-- 13 files changed, 147 insertions(+), 26 deletions(-) create mode 100644 app/api/auth/logout/route.ts create mode 100644 app/api/user/recovery/password/confirm/route.ts create mode 100644 app/api/user/recovery/password/route.ts create mode 100644 app/api/user/sessions/route.ts diff --git a/.env.sample b/.env.sample index dd5b5d5..7ae5172 100644 --- a/.env.sample +++ b/.env.sample @@ -1,4 +1,9 @@ # Appwrite APPWRITE_PROJECT_ID = "5f5f3d5d5f5f3" APPWRITE_ENDPOINT = "https://appwrite.io/v1" -APPWRITE_KEY = "G" \ No newline at end of file +APPWRITE_KEY = "G" + + +# Vercel +HTTPS = 1 # 0 for http, 1 for https +VERCEL_URL = localhost:3000 \ No newline at end of file diff --git a/app/api/auth/login/route.ts b/app/api/auth/login/route.ts index c0fa1e7..d5f72cb 100644 --- a/app/api/auth/login/route.ts +++ b/app/api/auth/login/route.ts @@ -33,11 +33,9 @@ export async function POST(req: NextRequest) { ); - - const sessionKey = session.secret; // setting the cookie - await SetAuthCookie(sessionKey); + await SetAuthCookie(session); const {account:SavedUserAccount} = await ClientAW(); diff --git a/app/api/auth/logout/route.ts b/app/api/auth/logout/route.ts new file mode 100644 index 0000000..4559c83 --- /dev/null +++ b/app/api/auth/logout/route.ts @@ -0,0 +1,13 @@ +import { ClientAW } from "@/appwrite_configs/config"; + +import { errorHandler, successHandler } from "../../handler"; + +export async function POST() { + try { + const { account } = await ClientAW(true); + await account.deleteSession("current"); + return successHandler({}, "Logged out successfully", 200); + } catch (e) { + return errorHandler(e); + } +} diff --git a/app/api/auth/oauth/github/login/route.ts b/app/api/auth/oauth/github/login/route.ts index 8bf129b..5b475d8 100644 --- a/app/api/auth/oauth/github/login/route.ts +++ b/app/api/auth/oauth/github/login/route.ts @@ -3,14 +3,15 @@ import * as sdk from "node-appwrite"; import { errorHandler } from "@/app/api/handler"; import { ClientAW } from "@/appwrite_configs/config"; +import { env } from "@/env"; export async function GET() { try { const { account } = await ClientAW(false); const result = await account.createOAuth2Token( sdk.OAuthProvider.Github, // provider - "http://localhost:3000/api/auth/oauth/github/login/success", // success (optional) - "http://localhost:3000/api/auth/oauth/github/login/failure", // failure (optional) + `${env.vercel.url}/auth/oauth/github/login/success`, // success (optional) + `${env.vercel.url}/auth/oauth/github/login/failure`, // failure (optional) [] // scopes (optional) ); diff --git a/app/api/auth/oauth/github/login/success/route.ts b/app/api/auth/oauth/github/login/success/route.ts index 4277448..d8f6020 100644 --- a/app/api/auth/oauth/github/login/success/route.ts +++ b/app/api/auth/oauth/github/login/success/route.ts @@ -23,10 +23,9 @@ export async function GET(req: NextRequest) { const { account: accountRoot } = await RootAW(); const session = await accountRoot.createSession(userId, secret); - const sessionKey = session.secret; // setting the cookie - await SetAuthCookie(sessionKey); + await SetAuthCookie(session); const { account: SavedUserAccount } = await ClientAW(); diff --git a/app/api/auth/oauth/github/register/route.ts b/app/api/auth/oauth/github/register/route.ts index a0bde15..208d5bb 100644 --- a/app/api/auth/oauth/github/register/route.ts +++ b/app/api/auth/oauth/github/register/route.ts @@ -3,14 +3,15 @@ import * as sdk from "node-appwrite"; import { errorHandler } from "@/app/api/handler"; import { ClientAW } from "@/appwrite_configs/config"; +import { env } from "@/env"; export async function GET() { try { const { account } = await ClientAW(false); const result = await account.createOAuth2Token( sdk.OAuthProvider.Github, // provider - "http://localhost:3000/api/auth/oauth/github/register/success", // success (optional) - "http://localhost:3000/api/auth/oauth/github/register/failure", // failure (optional) + `${env.vercel.url}/auth/oauth/github/register/success`, // success (optional) + `${env.vercel.url}/auth/oauth/github/register/failure`, // failure (optional) [] // scopes (optional) ); diff --git a/app/api/auth/oauth/github/register/success/route.ts b/app/api/auth/oauth/github/register/success/route.ts index 4277448..d8f6020 100644 --- a/app/api/auth/oauth/github/register/success/route.ts +++ b/app/api/auth/oauth/github/register/success/route.ts @@ -23,10 +23,9 @@ export async function GET(req: NextRequest) { const { account: accountRoot } = await RootAW(); const session = await accountRoot.createSession(userId, secret); - const sessionKey = session.secret; // setting the cookie - await SetAuthCookie(sessionKey); + await SetAuthCookie(session); const { account: SavedUserAccount } = await ClientAW(); diff --git a/app/api/auth/register/route.ts b/app/api/auth/register/route.ts index bf9df54..b596912 100644 --- a/app/api/auth/register/route.ts +++ b/app/api/auth/register/route.ts @@ -2,6 +2,7 @@ import type { NextRequest } from "next/server"; import { AppwriteException, ID } from "node-appwrite"; import { ClientAW, RootAW } from "@/appwrite_configs/config"; +import { env } from "@/env"; import { SetAuthCookie } from "@/helpers/sessionCookieFunctions"; import { registerSchema } from "@/validations/auth/registerSchema"; @@ -10,12 +11,12 @@ import { errorHandler, successHandler } from "../../handler"; export async function POST(req: NextRequest) { try { let body = {}; - try{ - body = await req.json() || {}; - }catch{ + try { + body = (await req.json()) || {}; + } catch { throw new AppwriteException("No body passed", 400); } - + const validation = await registerSchema.safeParse(body); if (!validation.success) { throw new AppwriteException(validation.error.errors[0].message, 400); @@ -39,14 +40,16 @@ export async function POST(req: NextRequest) { validatedData.email, validatedData.password ); - const sessionKey = session.secret; + // setting the cookie - await SetAuthCookie(sessionKey); + await SetAuthCookie(session); const { account: SavedUserAccount } = await ClientAW(); // sending email - await SavedUserAccount.createVerification("http://localhost:3000/api/user/email/verify"); + await SavedUserAccount.createVerification( + `${env.vercel.url}/user/email/verify` + ); // return the user return successHandler({ @@ -56,10 +59,9 @@ export async function POST(req: NextRequest) { name: user.name, verified: user.emailVerification, registrationDate: user.registration, - } + }, }); } catch (e) { return errorHandler(e); } } - diff --git a/app/api/user/email/verify/resend/route.ts b/app/api/user/email/verify/resend/route.ts index e65c410..a479d7c 100644 --- a/app/api/user/email/verify/resend/route.ts +++ b/app/api/user/email/verify/resend/route.ts @@ -3,6 +3,7 @@ import { AppwriteException } from "node-appwrite"; import { errorHandler, successHandler } from "@/app/api/handler"; import { ClientAW } from "@/appwrite_configs/config"; +import { env } from "@/env"; export async function POST(req: NextRequest) { try { @@ -32,7 +33,7 @@ export async function POST(req: NextRequest) { throw new AppwriteException("User Id does not match", 400); } - await account.createVerification("http://localhost:3000/api/user/email/verify"); + await account.createVerification(`${env.vercel.url}/user/email/verify`); return successHandler( {}, diff --git a/app/api/user/recovery/password/confirm/route.ts b/app/api/user/recovery/password/confirm/route.ts new file mode 100644 index 0000000..1218eef --- /dev/null +++ b/app/api/user/recovery/password/confirm/route.ts @@ -0,0 +1,55 @@ +import type { NextRequest } from "next/server"; +import { AppwriteException } from "node-appwrite"; + +import { errorHandler, successHandler } from "@/app/api/handler"; +import { ClientAW } from "@/appwrite_configs/config"; +import { recoveryPasswordConfirmSchema } from "@/validations/user/recoveryPasswordConfirm"; + +export async function POST(req: NextRequest) { + try { + const queryStore = req.nextUrl.searchParams; + + const isPresent = queryStore.has("secret") && queryStore.has("userId"); + + if (!isPresent) { + return errorHandler( + new AppwriteException("userId or secret not present", 400) + ); + } + + const secret = queryStore.get("secret") as string; + const userId = queryStore.get("userId") as string; + + // getting new password + let body = { + userId: null, + }; + try { + body = await req.json(); + } catch { + throw new AppwriteException("No body passed", 400); + } + + const validation = recoveryPasswordConfirmSchema.safeParse(body); + + if (!validation.success) { + throw new AppwriteException(validation.error.errors[0].message, 400); + } + + const validatedData = validation.data; + + const { account } = await ClientAW(false); + + await account.updateRecovery(userId, secret,validatedData.newPassword); + + return successHandler( + { + id: userId, + }, + "Passwrord reset successfully", + 201 + ); + } catch (e) { + return errorHandler(e); + } +} diff --git a/app/api/user/recovery/password/route.ts b/app/api/user/recovery/password/route.ts new file mode 100644 index 0000000..7775c92 --- /dev/null +++ b/app/api/user/recovery/password/route.ts @@ -0,0 +1,34 @@ +import { NextRequest } from "next/server"; +import { AppwriteException } from "node-appwrite"; + +import { errorHandler, successHandler } from "@/app/api/handler"; +import { ClientAW } from "@/appwrite_configs/config"; +import { recoveryPasswordSchema } from "@/validations/user/recoveryPassword"; + +export async function POST(req: NextRequest) { + try { + let body = {}; + try { + body = (await req.json()) || {}; + } catch { + throw new AppwriteException("No body passed", 400); + } + + const validation = recoveryPasswordSchema.safeParse(body); + if (!validation.success) { + throw new AppwriteException(validation.error.errors[0].message, 400); + } + + const { account } = await ClientAW(true); + + const validatedData = validation.data; + await account.createRecovery( + validatedData.email, + "http://localhost:3000/api/user/recovery/password/confirm" + ); + + return successHandler({}, "Recovery email sent successfully", 201); + } catch (e) { + return errorHandler(e); + } +} diff --git a/app/api/user/sessions/route.ts b/app/api/user/sessions/route.ts new file mode 100644 index 0000000..c9f34fe --- /dev/null +++ b/app/api/user/sessions/route.ts @@ -0,0 +1,13 @@ +import { ClientAW } from "@/appwrite_configs/config"; + +import { errorHandler, successHandler } from "../../handler"; + +export async function GET() { + try { + const { account } = await ClientAW(true); + const listSessions = await account.listSessions(); + return successHandler(listSessions, "Sessions fetched successfully", 200); + } catch (e) { + return errorHandler(e); + } +} diff --git a/helpers/sessionCookieFunctions.ts b/helpers/sessionCookieFunctions.ts index 7b79a33..634a900 100644 --- a/helpers/sessionCookieFunctions.ts +++ b/helpers/sessionCookieFunctions.ts @@ -1,6 +1,6 @@ import { ReadonlyRequestCookies } from "next/dist/server/web/spec-extension/adapters/request-cookies"; import { cookies } from "next/headers"; -import { AppwriteException } from "node-appwrite"; +import { AppwriteException, Models } from "node-appwrite"; import { env } from "@/env"; @@ -21,13 +21,13 @@ export const RetrieveAuthCookie = async ():Promise => { }; -export const SetAuthCookie = async (cookieValue: string) => { +export const SetAuthCookie = async (cookieData:Models.Session) => { const cookieStore = await cookies(); - cookieStore.set(env.auth.cookieName, cookieValue, { + cookieStore.set(env.auth.cookieName, cookieData.secret, { httpOnly:env.auth.cookieHttpOnly, secure:env.auth.cookieSecure, sameSite:env.auth.cookieSameSite , path:env.auth.cookiePath, - maxAge:env.auth.cookieMaxAge + expires:new Date(cookieData.expire) }) } \ No newline at end of file From 7715f713ea9652a51c72b548a6a8f48eb20b846d Mon Sep 17 00:00:00 2001 From: Aditya Date: Tue, 19 Nov 2024 16:53:47 +0530 Subject: [PATCH 2/2] #2 validation folder alo added in the fix along with full appwrite-auth with cookie fix --- validations/user/recoveryPassword.ts | 10 ++++++++++ validations/user/recoveryPasswordConfirm.ts | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 validations/user/recoveryPassword.ts create mode 100644 validations/user/recoveryPasswordConfirm.ts diff --git a/validations/user/recoveryPassword.ts b/validations/user/recoveryPassword.ts new file mode 100644 index 0000000..29bcaa3 --- /dev/null +++ b/validations/user/recoveryPassword.ts @@ -0,0 +1,10 @@ +import { z } from "zod"; + +export const recoveryPasswordSchema = z.object({ + email: z + .string({ + required_error: "Email is required", + invalid_type_error: "Email must be a string", + }) + .email({message: "Invalid email"}), +}) \ No newline at end of file diff --git a/validations/user/recoveryPasswordConfirm.ts b/validations/user/recoveryPasswordConfirm.ts new file mode 100644 index 0000000..5adfca7 --- /dev/null +++ b/validations/user/recoveryPasswordConfirm.ts @@ -0,0 +1,19 @@ +import { z } from "zod"; + +export const recoveryPasswordConfirmSchema = z.object({ + newPassword: z + .string({ + required_error: "New password is required", + invalid_type_error: "New password must be a string", + }) + .min(6, {message: "New password must be at least 6 characters long"}), + confirmPassword: z + .string({ + required_error: "Confirm password is required", + invalid_type_error: "Confirm password must be a string", + }) + .min(6, {message: "Confirm password must be at least 6 characters long"}), +}).refine(data => data.newPassword === data.confirmPassword, { + message: "New password and confirm password must match", + path: ["confirmPassword"], +}); \ No newline at end of file