diff --git a/backend/app/controllers/auth/social_controller.ts b/backend/app/controllers/auth/social_controller.ts new file mode 100644 index 0000000..344ba31 --- /dev/null +++ b/backend/app/controllers/auth/social_controller.ts @@ -0,0 +1,13 @@ +import { HttpContext } from "@adonisjs/core/http"; + +import AuthSocialService from "#services/auth_social_service"; + +export default class SocialController { + async redirect({ ally, params }: HttpContext) { + await ally.use(params["provider"]).redirect(); + } + + async callback(ctx: HttpContext) { + await AuthSocialService.handleCallback(ctx); + } +} diff --git a/backend/app/services/auth/user/user-provider.ts b/backend/app/services/auth/user/user-provider.ts index e3d6bc6..ceb3dec 100644 --- a/backend/app/services/auth/user/user-provider.ts +++ b/backend/app/services/auth/user/user-provider.ts @@ -1,40 +1,23 @@ import LocalLoginHandler from "#services/auth/local/local-login.handler"; import TokenHandler from "#services/auth/token/token.handler"; import UserHandler from "#services/auth/user/user.handler"; -import { User } from "#services/types/user"; -async function getUser( +async function loginOrCreate( username: string, provider: string, providerId: string, -): Promise { - let user; +) { try { - user = await UserHandler.get(provider, providerId); + await UserHandler.get(provider, providerId); } catch { - user = await UserHandler.create(username, provider, providerId); + await UserHandler.create(username, provider, providerId); } - return user; -} - -async function loginOrCreate( - username: string, - provider: string, - providerId: string, -): Promise<{ - user: User; - tokens: { accessToken: string; refreshToken: string }; -}> { - const user = await getUser(username, provider, providerId); - await UserHandler.valid(username); await LocalLoginHandler.createDefaultLocalLoginIfNoneIsFound(username); - const tokens = await TokenHandler.createTokens(username); - - return { user: user, tokens: tokens }; + return await TokenHandler.createTokens(username); } const UserProvider = { diff --git a/backend/app/services/auth_social_service.ts b/backend/app/services/auth_social_service.ts new file mode 100644 index 0000000..9c3ba6b --- /dev/null +++ b/backend/app/services/auth_social_service.ts @@ -0,0 +1,35 @@ +import { SocialProviders } from "@adonisjs/ally/types"; +import { HttpContext } from "@adonisjs/core/http"; + +import UserProvider from "#services/auth/user/user-provider"; +import { retrieveRefererPath } from "#services/config/api-path"; +import { BlEnv } from "#services/config/env"; + +async function handleCallback(ctx: HttpContext) { + const provider: keyof SocialProviders = ctx.params["provider"]; + const social = ctx.ally.use(provider); + + if (social.accessDenied() || social.stateMisMatch() || social.hasError()) { + return ctx.response.redirect(`${BlEnv.CLIENT_URI}auth/social/failure`); + } + + const user = await social.user(); + + const { accessToken, refreshToken } = await UserProvider.loginOrCreate( + user.email, + provider, + user.id, + ); + + return ctx.response.redirect( + `${ + retrieveRefererPath(ctx.request.headers()) ?? BlEnv.CLIENT_URI + }auth/token?access_token=${accessToken}&refresh_token=${refreshToken}`, + ); +} + +const AuthSocialService = { + handleCallback, +}; + +export default AuthSocialService; diff --git a/backend/start/routes.ts b/backend/start/routes.ts index e0e1cdc..2b5783d 100644 --- a/backend/start/routes.ts +++ b/backend/start/routes.ts @@ -6,72 +6,22 @@ import * as Sentry from "@sentry/node"; import configureMongoose from "#config/database"; import LocalAuth from "#services/auth/local/local.auth"; import TokenEndpoint from "#services/auth/token/token.endpoint"; -import UserProvider from "#services/auth/user/user-provider"; import CollectionEndpointCreator from "#services/collection-endpoint/collection-endpoint-creator"; -import { retrieveRefererPath } from "#services/config/api-path"; -import { APP_CONFIG } from "#services/config/application-config"; import setupPassport from "#services/config/auth"; import { BlEnv } from "#services/config/env"; -router.get("/auth/facebook", ({ ally }) => { - return ally.use("facebook").redirect(); -}); -router.get("/auth/facebook/callback", async ({ ally, request, response }) => { - const fb = ally.use("facebook"); - if (fb.accessDenied()) { - return "You have cancelled the login process"; - } - if (fb.stateMisMatch()) { - return "We are unable to verify the request. Please try again"; - } - - if (fb.hasError()) { - return fb.getError(); - } - - const user = await fb.user(); - const provider = APP_CONFIG.login.facebook.name; - - const userAndTokens = await UserProvider.loginOrCreate( - user.email, - provider, - user.id, - ); - const redirectUrl = `${ - retrieveRefererPath(request.headers()) ?? BlEnv.CLIENT_URI - }auth/token?access_token=${userAndTokens.tokens.accessToken}&refresh_token=${userAndTokens.tokens.refreshToken}`; - return response.redirect(redirectUrl); -}); - -router.get("/auth/google", ({ ally }) => { - return ally.use("google").redirect(); -}); -router.get("/auth/google/callback", async ({ ally, request, response }) => { - const google = ally.use("google"); - if (google.accessDenied()) { - return "You have cancelled the login process"; - } - if (google.stateMisMatch()) { - return "We are unable to verify the request. Please try again"; - } - - if (google.hasError()) { - return google.getError(); - } - - const user = await google.user(); - const provider = APP_CONFIG.login.google.name; - - const userAndTokens = await UserProvider.loginOrCreate( - user.email, - provider, - user.id, - ); - const redirectUrl = `${ - retrieveRefererPath(request.headers()) ?? BlEnv.CLIENT_URI - }auth/token?access_token=${userAndTokens.tokens.accessToken}&refresh_token=${userAndTokens.tokens.refreshToken}`; - return response.redirect(redirectUrl); -}); +const AuthSocialController = () => + import("#controllers/auth/social_controller"); + +/** + * auth social + */ +router + .get("/auth/:provider/redirect", [AuthSocialController, "redirect"]) + .as("auth.social.redirect"); +router + .get("/auth/:provider/callback", [AuthSocialController, "callback"]) + .as("auth.social.callback"); setupPassport(); LocalAuth.generateEndpoints(); diff --git a/backend/tests/user-provider.spec.ts b/backend/tests/user-provider.spec.ts index 77b9a57..01a6e4a 100644 --- a/backend/tests/user-provider.spec.ts +++ b/backend/tests/user-provider.spec.ts @@ -79,7 +79,7 @@ test.group("UserProvider", (group) => { return expect( UserProvider.loginOrCreate("username@mail.com", "local", "abcdefg"), - ).to.eventually.be.eql({ user: user, tokens: tokens }); + ).to.eventually.be.eql(tokens); }); test("loginOrCreate() - should resolve with a user object and tokens", async () => { @@ -93,6 +93,6 @@ test.group("UserProvider", (group) => { return expect( UserProvider.loginOrCreate("username@mail.com", "local", "abcdefg"), - ).to.eventually.be.eql({ user: user, tokens: tokens }); + ).to.eventually.be.eql(tokens); }); }); diff --git a/frontend/src/utils/bl-config.ts b/frontend/src/utils/bl-config.ts index 2f3c7f8..33548b0 100644 --- a/frontend/src/utils/bl-config.ts +++ b/frontend/src/utils/bl-config.ts @@ -18,10 +18,10 @@ const BL_CONFIG = { url: "auth/local/login", }, facebook: { - url: "auth/facebook", + url: "auth/facebook/redirect", }, google: { - url: "auth/google", + url: "auth/google/redirect", }, localStorageKeys: { redirect: "bl-redirect",