package example import AuthService._ import example.pocketbase.Api import upickle.default._ import example.pocketbase.Models._ case class AuthService()(implicit cc: castor.Context, log: cask.Logger) extends cask.Routes { @cask.get("/") def getIndex(request: cask.Request) = { println("hellololo") val authCookieOpt = request.cookies.get(authCookieName) authCookieOpt match { case None => cask.Redirect("/login") case Some(authCookie) => val jwt = authCookie.value ??? } } @cask.get("/login") def getLoginPage() = { // render auth page with the available oauth providers val authOptions = pocketbaseApi.listAuthMethods() val options = s"got following auth opitons: $authOptions" // save states and verifiers into cookie val oauthVerificationCookie = authOptions.toOauthCookieInfo() val yoyo = write(oauthVerificationCookie) val githubOption = authOptions.authProviders.find(_.name == "github") val githubRedirect = githubOption.map(_.authUrl).getOrElse("") ++ getRedirectUrl("github") val html = s"""
$options
will use ${githubRedirect}
Go to github """ cask.Response( html, headers = Seq("Content-Type" -> "text/html;charset=UTF-8"), cookies = Seq(cask.Cookie(name = oauthVerifiersCookieName, value = yoyo)) ) } /** @oauthVerifiers * \- required cookie from start of oauth initialization cask will fail * request if this is not present */ @cask.get(s"${baseRedirectUrl}/:provider") def receiveOauthRedirect( provider: String, state: String, code: String, request: cask.Request ) = { println( s"received redirect for $provider with state: $state and code: $code" ) val authVerifierOpt = request.cookies .get(oauthVerifiersCookieName) .map(cookie => read[OauthInfoCookie](cookie.value)) .flatMap(_.providersVerification.find(_.name == provider)) val resultOpt = for { authVerifier <- authVerifierOpt _ <- Some(()).filter(_ => state == authVerifier.state) pocketbaseAuthResult <- pocketbaseApi .authWithOauth( provider = provider, code = code, verifier = authVerifier.codeVerifier, redirectUrl = getRedirectUrl(provider) ) .toOption } yield pocketbaseAuthResult val okMessageFirst = resultOpt match { case None => // i guess with the SSR i'll need to return message about unsuccessful auth? s"""user should be already created, current jwt : ${result.token}
the account is on ${result.record.email} and ${result.record.username}
""" } /* * get provider from path param, get verifiers and state from cookie, if cookie not present - abort, if state doesn't fit one from redirect params - abort issue 'auth with oauth 2' and based on response code - set the cookie with jwt and delete the state\verifiers cookie but then what? i guess call for redirect to root page again? which should trigger auth check and main page render? */ import java.time.Instant cask.Response( okMessageFirst, headers = Seq("Content-Type" -> "text/html;charset=UTF-8"), cookies = Seq( cask.Cookie( name = oauthVerifiersCookieName, value = "", expires = Instant.now() ) ) ) } initialize() } object AuthService { val authCookieName = "auth" // this is to share info between /auth page and /redirect-landing // can be improved by generating same state and codeVerifier for all oauth links // and can also be stored purely on the backend - a tad more secure, i guess // but if cookie is under https, should be ok val oauthVerifiersCookieName = "oauthVerifiers" val pocketbaseApi = Api("http://127.0.0.1:8090") val selfUri = "http://127.0.0.1:8080" // i guess TOOD put that into config val baseRedirectUrl = "/api/oauth2-redirect" /* * let's have separate redirect urls for different providers * if we'd have same url for all providers, we'll need way to figure out from state which one was called on front end. * it's possible, but path param looks simpler right now * github redirect : http://127.0.0.1:8080/api/oauth2-redirect/github */ def getRedirectUrl(provider: String): String = s"${selfUri}${baseRedirectUrl}/${provider}" }