mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-12-23 17:13:40 +03:00
feat: Add Twitter/X auth
This commit is contained in:
parent
ec15625d15
commit
e5658b9565
@ -179,6 +179,9 @@ studio = do
|
||||
[ "discord"
|
||||
| isJust $ AS.App.Auth.discord methods
|
||||
],
|
||||
[ "twitter"
|
||||
| isJust $ AS.App.Auth.twitter methods
|
||||
],
|
||||
[ "google"
|
||||
| isJust $ AS.App.Auth.google methods
|
||||
],
|
||||
|
@ -117,6 +117,9 @@ const keycloakSignInUrl = `${config.apiUrl}{= keycloakSignInPath =}`
|
||||
{=# enabledProviders.isGitHubAuthEnabled =}
|
||||
const gitHubSignInUrl = `${config.apiUrl}{= gitHubSignInPath =}`
|
||||
{=/ enabledProviders.isGitHubAuthEnabled =}
|
||||
{=# enabledProviders.isTwitterAuthEnabled =}
|
||||
const twitterSignInUrl = `${config.apiUrl}{= twitterSignInPath =}`
|
||||
{=/ enabledProviders.isTwitterAuthEnabled =}
|
||||
|
||||
{=!
|
||||
// Since we allow users to add additional fields to the signup form, we don't
|
||||
@ -208,6 +211,10 @@ export const LoginSignupForm = ({
|
||||
{=# enabledProviders.isGitHubAuthEnabled =}
|
||||
<SocialButton href={gitHubSignInUrl}><SocialIcons.GitHub/></SocialButton>
|
||||
{=/ enabledProviders.isGitHubAuthEnabled =}
|
||||
|
||||
{=# enabledProviders.isTwitterAuthEnabled =}
|
||||
<SocialButton href={twitterSignInUrl}><SocialIcons.Twitter/></SocialButton>
|
||||
{=/ enabledProviders.isTwitterAuthEnabled =}
|
||||
</SocialAuthButtons>
|
||||
</SocialAuth>
|
||||
{=/ isSocialAuthEnabled =}
|
||||
|
@ -65,3 +65,14 @@ export const Discord = () => (
|
||||
<path d="M13.545 2.907a13.227 13.227 0 00-3.257-1.011.05.05 0 00-.052.025c-.141.25-.297.577-.406.833a12.19 12.19 0 00-3.658 0 8.258 8.258 0 00-.412-.833.051.051 0 00-.052-.025c-1.125.194-2.22.534-3.257 1.011a.041.041 0 00-.021.018C.356 6.024-.213 9.047.066 12.032c.001.014.01.028.021.037a13.276 13.276 0 003.995 2.02.05.05 0 00.056-.019c.308-.42.582-.863.818-1.329a.05.05 0 00-.01-.059.051.051 0 00-.018-.011 8.875 8.875 0 01-1.248-.595.05.05 0 01-.02-.066.051.051 0 01.015-.019c.084-.063.168-.129.248-.195a.05.05 0 01.051-.007c2.619 1.196 5.454 1.196 8.041 0a.052.052 0 01.053.007c.08.066.164.132.248.195a.051.051 0 01-.004.085 8.254 8.254 0 01-1.249.594.05.05 0 00-.03.03.052.052 0 00.003.041c.24.465.515.909.817 1.329a.05.05 0 00.056.019 13.235 13.235 0 004.001-2.02.049.049 0 00.021-.037c.334-3.451-.559-6.449-2.366-9.106a.034.034 0 00-.02-.019zm-8.198 7.307c-.789 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.45.73 1.438 1.613 0 .888-.637 1.612-1.438 1.612zm5.316 0c-.788 0-1.438-.724-1.438-1.612 0-.889.637-1.613 1.438-1.613.807 0 1.451.73 1.438 1.613 0 .888-.631 1.612-1.438 1.612z" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
export const Twitter = () => (
|
||||
<svg
|
||||
className={defaultStyles()}
|
||||
aria-hidden="true"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
|
||||
</svg>
|
||||
)
|
||||
|
@ -44,6 +44,7 @@ export type PossibleProviderData = {
|
||||
google: OAuthProviderData;
|
||||
keycloak: OAuthProviderData;
|
||||
github: OAuthProviderData;
|
||||
twitter: OAuthProviderData;
|
||||
}
|
||||
|
||||
// PUBLIC API
|
||||
|
@ -18,6 +18,9 @@ export * from './keycloak'
|
||||
{=# isGitHubAuthEnabled =}
|
||||
export * from './github'
|
||||
{=/ isGitHubAuthEnabled =}
|
||||
{=# isTwitterAuthEnabled =}
|
||||
export * from './twitter'
|
||||
{=/ isTwitterAuthEnabled =}
|
||||
export {
|
||||
default as useAuth,
|
||||
getMe,
|
||||
|
@ -0,0 +1,2 @@
|
||||
// PUBLIC API
|
||||
export { signInUrl as twitterSignInUrl } from '../../auth/helpers/Twitter'
|
@ -20,6 +20,9 @@ export { SignInButton as KeycloakSignInButton } from '../../auth/helpers/Keycloa
|
||||
{=# isGitHubAuthEnabled =}
|
||||
export { SignInButton as GitHubSignInButton } from '../../auth/helpers/GitHub'
|
||||
{=/ isGitHubAuthEnabled =}
|
||||
{=# isTwitterAuthEnabled =}
|
||||
export { SignInButton as TwitterSignInButton } from '../../auth/helpers/Twitter'
|
||||
{=/ isTwitterAuthEnabled =}
|
||||
export {
|
||||
FormError,
|
||||
FormInput,
|
||||
|
@ -142,5 +142,8 @@ export type OAuthData = {
|
||||
{=# enabledProviders.isKeycloakAuthEnabled =}
|
||||
| { providerName: 'keycloak'; tokens: import('arctic').KeycloakTokens }
|
||||
{=/ enabledProviders.isKeycloakAuthEnabled =}
|
||||
{=# enabledProviders.isTwitterAuthEnabled =}
|
||||
| { providerName: 'twitter'; tokens: import('arctic').TwitterTokens }
|
||||
{=/ enabledProviders.isTwitterAuthEnabled =}
|
||||
| never
|
||||
)
|
||||
|
@ -15,6 +15,10 @@ export { github } from './providers/github.js';
|
||||
// PUBLIC API
|
||||
export { keycloak } from './providers/keycloak.js';
|
||||
{=/ enabledProviders.isKeycloakAuthEnabled =}
|
||||
{=# enabledProviders.isTwitterAuthEnabled =}
|
||||
// PUBLIC API
|
||||
export { twitter } from './providers/twitter.js';
|
||||
{=/ enabledProviders.isTwitterAuthEnabled =}
|
||||
|
||||
// PRIVATE API
|
||||
export {
|
||||
|
@ -0,0 +1,28 @@
|
||||
{{={= =}=}}
|
||||
import { Twitter } from "arctic";
|
||||
|
||||
import { defineProvider } from "../provider.js";
|
||||
import { ensureEnvVarsForProvider } from "../env.js";
|
||||
import { getRedirectUriForCallback } from "../redirect.js";
|
||||
|
||||
const id = "{= providerId =}";
|
||||
const displayName = "{= displayName =}";
|
||||
|
||||
const env = ensureEnvVarsForProvider(
|
||||
["TWITTER_CLIENT_ID", "TWITTER_CLIENT_SECRET"],
|
||||
displayName
|
||||
);
|
||||
|
||||
const oAuthClient = new Twitter(
|
||||
env.TWITTER_CLIENT_ID,
|
||||
env.TWITTER_CLIENT_SECRET,
|
||||
getRedirectUriForCallback(id).toString(),
|
||||
);
|
||||
|
||||
// PUBLIC API
|
||||
export const twitter = defineProvider({
|
||||
id,
|
||||
displayName,
|
||||
env,
|
||||
oAuthClient,
|
||||
});
|
@ -49,6 +49,9 @@ export type AuthUserData = Omit<CompleteUserEntityWithAuth, '{= authFieldOnUserE
|
||||
{=# enabledProviders.isGitHubAuthEnabled =}
|
||||
github: Expand<UserFacingProviderData<'github'>> | null
|
||||
{=/ enabledProviders.isGitHubAuthEnabled =}
|
||||
{=# enabledProviders.isTwitterAuthEnabled =}
|
||||
twitter: Expand<UserFacingProviderData<'twitter'>> | null
|
||||
{=/ enabledProviders.isTwitterAuthEnabled =}
|
||||
},
|
||||
}
|
||||
|
||||
@ -111,6 +114,9 @@ This should never happen, but it did which means there is a bug in the code.`)
|
||||
{=# enabledProviders.isGitHubAuthEnabled =}
|
||||
github: getProviderInfo<'github'>({= authFieldOnUserEntityName =}, 'github'),
|
||||
{=/ enabledProviders.isGitHubAuthEnabled =}
|
||||
{=# enabledProviders.isTwitterAuthEnabled =}
|
||||
twitter: getProviderInfo<'twitter'>({= authFieldOnUserEntityName =}, 'twitter'),
|
||||
{=/ enabledProviders.isTwitterAuthEnabled =}
|
||||
}
|
||||
return {
|
||||
...rest,
|
||||
|
@ -0,0 +1,68 @@
|
||||
{{={= =}=}}
|
||||
|
||||
import type { ProviderConfig } from "wasp/auth/providers/types";
|
||||
import { twitter } from "wasp/server/auth";
|
||||
import { mergeDefaultAndUserConfig } from "../oauth/config.js";
|
||||
import { createOAuthProviderRouter } from "../oauth/handler.js";
|
||||
|
||||
{=# userSignupFields.isDefined =}
|
||||
{=& userSignupFields.importStatement =}
|
||||
const _waspUserSignupFields = {= userSignupFields.importIdentifier =}
|
||||
{=/ userSignupFields.isDefined =}
|
||||
{=^ userSignupFields.isDefined =}
|
||||
const _waspUserSignupFields = undefined
|
||||
{=/ userSignupFields.isDefined =}
|
||||
{=# configFn.isDefined =}
|
||||
{=& configFn.importStatement =}
|
||||
const _waspUserDefinedConfigFn = {= configFn.importIdentifier =}
|
||||
{=/ configFn.isDefined =}
|
||||
{=^ configFn.isDefined =}
|
||||
const _waspUserDefinedConfigFn = undefined
|
||||
{=/ configFn.isDefined =}
|
||||
|
||||
const _waspConfig: ProviderConfig = {
|
||||
id: twitter.id,
|
||||
displayName: twitter.displayName,
|
||||
createRouter(provider) {
|
||||
const config = mergeDefaultAndUserConfig({
|
||||
scopes: {=& requiredScopes =},
|
||||
}, _waspUserDefinedConfigFn);
|
||||
|
||||
async function getTwitterProfile(accessToken: string): Promise<{
|
||||
providerProfile: unknown;
|
||||
providerUserId: string;
|
||||
}> {
|
||||
const response = await fetch("https://api.twitter.com/2/users/me", {
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
});
|
||||
|
||||
const jsonResponse = await response.json();
|
||||
|
||||
const providerProfile = jsonResponse.data as {
|
||||
id?: string;
|
||||
name?: string;
|
||||
username?: string;
|
||||
};
|
||||
|
||||
|
||||
if (!providerProfile.id) {
|
||||
throw new Error("Invalid profile");
|
||||
}
|
||||
|
||||
return { providerProfile, providerUserId: providerProfile.id };
|
||||
}
|
||||
|
||||
return createOAuthProviderRouter({
|
||||
provider,
|
||||
oAuthType: 'OAuth2WithPKCE',
|
||||
userSignupFields: _waspUserSignupFields,
|
||||
getAuthorizationUrl: ({ state, codeVerifier }) => twitter.oAuthClient.createAuthorizationURL(state, codeVerifier, config),
|
||||
getProviderTokens: ({ code, codeVerifier }) => twitter.oAuthClient.validateAuthorizationCode(code, codeVerifier),
|
||||
getProviderInfo: ({ accessToken }) => getTwitterProfile(accessToken),
|
||||
});
|
||||
},
|
||||
}
|
||||
|
||||
export default _waspConfig;
|
@ -22,3 +22,7 @@ GITHUB_CLIENT_SECRET='dummy-gh-client-secret'
|
||||
# Dummy values here will allow app to run, but you will need real values to get Discord Auth to work.
|
||||
DISCORD_CLIENT_SECRET='dummy-discord-client-secret'
|
||||
DISCORD_CLIENT_ID='dummy-discord-client-id'
|
||||
|
||||
# Dummy values here will allow app to run, but you will need real values to get Twitter Auth to work.
|
||||
TWITTER_CLIENT_SECRET='dummy-twitter-client-secret'
|
||||
TWITTER_CLIENT_ID='dummy-twitter-client-id'
|
||||
|
@ -26,6 +26,10 @@ app todoApp {
|
||||
configFn: import { config } from "@src/auth/github.js",
|
||||
userSignupFields: import { userSignupFields } from "@src/auth/github.js"
|
||||
},
|
||||
twitter: {
|
||||
configFn: import { config } from "@src/auth/twitter.js",
|
||||
userSignupFields: import { userSignupFields } from "@src/auth/twitter.js"
|
||||
},
|
||||
// keycloak: {},
|
||||
email: {
|
||||
userSignupFields: import { userSignupFields } from "@src/auth/email",
|
||||
|
10
waspc/examples/todoApp/src/auth/twitter.ts
Normal file
10
waspc/examples/todoApp/src/auth/twitter.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { defineUserSignupFields } from 'wasp/server/auth'
|
||||
|
||||
export function config() {
|
||||
console.log('Inside user-supplied Twitter config')
|
||||
return {
|
||||
scopes: ['users.read', 'tweet.read'],
|
||||
}
|
||||
}
|
||||
|
||||
export const userSignupFields = defineUserSignupFields({})
|
@ -14,6 +14,7 @@ module Wasp.AppSpec.App.Auth
|
||||
isKeycloakAuthEnabled,
|
||||
isGitHubAuthEnabled,
|
||||
isEmailAuthEnabled,
|
||||
isTwitterAuthEnabled,
|
||||
userSignupFieldsForEmailAuth,
|
||||
userSignupFieldsForUsernameAuth,
|
||||
userSignupFieldsForExternalAuth,
|
||||
@ -49,6 +50,7 @@ data AuthMethods = AuthMethods
|
||||
google :: Maybe ExternalAuthConfig,
|
||||
gitHub :: Maybe ExternalAuthConfig,
|
||||
keycloak :: Maybe ExternalAuthConfig,
|
||||
twitter :: Maybe ExternalAuthConfig,
|
||||
email :: Maybe EmailAuthConfig
|
||||
}
|
||||
deriving (Show, Eq, Data)
|
||||
@ -83,7 +85,8 @@ isExternalAuthEnabled auth =
|
||||
[ isDiscordAuthEnabled,
|
||||
isGoogleAuthEnabled,
|
||||
isGitHubAuthEnabled,
|
||||
isKeycloakAuthEnabled
|
||||
isKeycloakAuthEnabled,
|
||||
isTwitterAuthEnabled
|
||||
]
|
||||
|
||||
isDiscordAuthEnabled :: Auth -> Bool
|
||||
@ -98,6 +101,9 @@ isKeycloakAuthEnabled = isJust . keycloak . methods
|
||||
isGitHubAuthEnabled :: Auth -> Bool
|
||||
isGitHubAuthEnabled = isJust . gitHub . methods
|
||||
|
||||
isTwitterAuthEnabled :: Auth -> Bool
|
||||
isTwitterAuthEnabled = isJust . twitter . methods
|
||||
|
||||
isEmailAuthEnabled :: Auth -> Bool
|
||||
isEmailAuthEnabled = isJust . email . methods
|
||||
|
||||
|
@ -55,6 +55,14 @@ discordAuthProvider =
|
||||
OA._requiredScope = ["identify"]
|
||||
}
|
||||
|
||||
twitterAuthProvider :: OA.OAuthAuthProvider
|
||||
twitterAuthProvider =
|
||||
OA.OAuthAuthProvider
|
||||
{ OA._providerId = fromJust $ makeProviderId "twitter",
|
||||
OA._displayName = "Twitter",
|
||||
OA._requiredScope = ["users.read", "tweet.read"]
|
||||
}
|
||||
|
||||
getEnabledAuthProvidersJson :: AS.Auth.Auth -> Aeson.Value
|
||||
getEnabledAuthProvidersJson auth =
|
||||
object
|
||||
@ -62,6 +70,7 @@ getEnabledAuthProvidersJson auth =
|
||||
"isGoogleAuthEnabled" .= AS.Auth.isGoogleAuthEnabled auth,
|
||||
"isKeycloakAuthEnabled" .= AS.Auth.isKeycloakAuthEnabled auth,
|
||||
"isGitHubAuthEnabled" .= AS.Auth.isGitHubAuthEnabled auth,
|
||||
"isTwitterAuthEnabled" .= AS.Auth.isTwitterAuthEnabled auth,
|
||||
"isUsernameAndPasswordAuthEnabled" .= AS.Auth.isUsernameAndPasswordAuthEnabled auth,
|
||||
"isEmailAuthEnabled" .= AS.Auth.isEmailAuthEnabled auth
|
||||
]
|
||||
|
@ -11,6 +11,7 @@ import Wasp.Generator.AuthProviders
|
||||
gitHubAuthProvider,
|
||||
googleAuthProvider,
|
||||
keycloakAuthProvider,
|
||||
twitterAuthProvider,
|
||||
)
|
||||
import qualified Wasp.Generator.AuthProviders as AuthProviders
|
||||
import qualified Wasp.Generator.AuthProviders.OAuth as OAuth
|
||||
@ -124,6 +125,7 @@ genLoginSignupForm auth =
|
||||
"googleSignInPath" .= OAuth.serverLoginUrl googleAuthProvider,
|
||||
"keycloakSignInPath" .= OAuth.serverLoginUrl keycloakAuthProvider,
|
||||
"gitHubSignInPath" .= OAuth.serverLoginUrl gitHubAuthProvider,
|
||||
"twitterSignInPath" .= OAuth.serverLoginUrl twitterAuthProvider,
|
||||
"enabledProviders" .= AuthProviders.getEnabledAuthProvidersJson auth
|
||||
]
|
||||
areBothSocialAndPasswordBasedAuthEnabled = AS.Auth.isExternalAuthEnabled auth && isAnyPasswordBasedAuthEnabled
|
||||
|
@ -12,6 +12,7 @@ import Wasp.Generator.AuthProviders
|
||||
gitHubAuthProvider,
|
||||
googleAuthProvider,
|
||||
keycloakAuthProvider,
|
||||
twitterAuthProvider,
|
||||
)
|
||||
import Wasp.Generator.AuthProviders.OAuth (OAuthAuthProvider)
|
||||
import qualified Wasp.Generator.AuthProviders.OAuth as OAuth
|
||||
@ -32,13 +33,15 @@ genHelpers auth =
|
||||
[ [discordHelpers | AS.Auth.isDiscordAuthEnabled auth],
|
||||
[gitHubHelpers | AS.Auth.isGitHubAuthEnabled auth],
|
||||
[googleHelpers | AS.Auth.isGoogleAuthEnabled auth],
|
||||
[keycloakHelpers | AS.Auth.isKeycloakAuthEnabled auth]
|
||||
[keycloakHelpers | AS.Auth.isKeycloakAuthEnabled auth],
|
||||
[twitterHelpers | AS.Auth.isTwitterAuthEnabled auth]
|
||||
]
|
||||
where
|
||||
discordHelpers = mkHelpersFd discordAuthProvider [relfile|Discord.tsx|]
|
||||
gitHubHelpers = mkHelpersFd gitHubAuthProvider [relfile|GitHub.tsx|]
|
||||
googleHelpers = mkHelpersFd googleAuthProvider [relfile|Google.tsx|]
|
||||
keycloakHelpers = mkHelpersFd keycloakAuthProvider [relfile|Keycloak.tsx|]
|
||||
twitterHelpers = mkHelpersFd twitterAuthProvider [relfile|Twitter.tsx|]
|
||||
|
||||
mkHelpersFd :: OAuthAuthProvider -> Path' Rel' File' -> FileDraft
|
||||
mkHelpersFd provider helpersFp =
|
||||
|
@ -30,6 +30,7 @@ genNewClientAuth spec =
|
||||
<++> genAuthGoogle auth
|
||||
<++> genAuthKeycloak auth
|
||||
<++> genAuthGitHub auth
|
||||
<++> genAuthTwitter auth
|
||||
where
|
||||
maybeAuth = AS.App.auth $ snd $ getApp spec
|
||||
|
||||
@ -87,5 +88,11 @@ genAuthGitHub auth =
|
||||
then sequence [genFileCopy [relfile|client/auth/github.ts|]]
|
||||
else return []
|
||||
|
||||
genAuthTwitter :: AS.Auth.Auth -> Generator [FileDraft]
|
||||
genAuthTwitter auth =
|
||||
if AS.Auth.isTwitterAuthEnabled auth
|
||||
then sequence [genFileCopy [relfile|client/auth/twitter.ts|]]
|
||||
else return []
|
||||
|
||||
genFileCopy :: Path' (Rel SdkTemplatesDir) File' -> Generator FileDraft
|
||||
genFileCopy = return . C.mkTmplFd
|
||||
|
@ -14,7 +14,7 @@ import qualified Wasp.AppSpec.App.Auth as AS.App.Auth
|
||||
import qualified Wasp.AppSpec.App.Auth as AS.Auth
|
||||
import qualified Wasp.AppSpec.App.Dependency as AS.Dependency
|
||||
import qualified Wasp.AppSpec.Valid as AS.Valid
|
||||
import Wasp.Generator.AuthProviders (discordAuthProvider, getEnabledAuthProvidersJson, gitHubAuthProvider, googleAuthProvider, keycloakAuthProvider)
|
||||
import Wasp.Generator.AuthProviders (discordAuthProvider, getEnabledAuthProvidersJson, gitHubAuthProvider, googleAuthProvider, keycloakAuthProvider, twitterAuthProvider)
|
||||
import Wasp.Generator.AuthProviders.OAuth
|
||||
( OAuthAuthProvider,
|
||||
clientOAuthCallbackPath,
|
||||
@ -43,6 +43,7 @@ genOAuth auth
|
||||
<++> genOAuthProvider googleAuthProvider (AS.Auth.google . AS.Auth.methods $ auth)
|
||||
<++> genOAuthProvider keycloakAuthProvider (AS.Auth.keycloak . AS.Auth.methods $ auth)
|
||||
<++> genOAuthProvider gitHubAuthProvider (AS.Auth.gitHub . AS.Auth.methods $ auth)
|
||||
<++> genOAuthProvider twitterAuthProvider (AS.Auth.twitter . AS.Auth.methods $ auth)
|
||||
| otherwise = return []
|
||||
where
|
||||
genFileCopy = return . C.mkTmplFd
|
||||
|
@ -25,6 +25,7 @@ import Wasp.Generator.AuthProviders
|
||||
gitHubAuthProvider,
|
||||
googleAuthProvider,
|
||||
keycloakAuthProvider,
|
||||
twitterAuthProvider,
|
||||
)
|
||||
import Wasp.Generator.AuthProviders.OAuth (OAuthAuthProvider)
|
||||
import qualified Wasp.Generator.AuthProviders.OAuth as OAuth
|
||||
@ -45,6 +46,7 @@ genOAuthAuth auth
|
||||
<++> genOAuthProvider googleAuthProvider (AS.Auth.google . AS.Auth.methods $ auth)
|
||||
<++> genOAuthProvider keycloakAuthProvider (AS.Auth.keycloak . AS.Auth.methods $ auth)
|
||||
<++> genOAuthProvider gitHubAuthProvider (AS.Auth.gitHub . AS.Auth.methods $ auth)
|
||||
<++> genOAuthProvider twitterAuthProvider (AS.Auth.twitter . AS.Auth.methods $ auth)
|
||||
| otherwise = return []
|
||||
|
||||
genOAuthHelpers :: AS.Auth.Auth -> Generator [FileDraft]
|
||||
|
@ -30,6 +30,7 @@ import Wasp.Generator.AuthProviders
|
||||
googleAuthProvider,
|
||||
keycloakAuthProvider,
|
||||
localAuthProvider,
|
||||
twitterAuthProvider,
|
||||
)
|
||||
import qualified Wasp.Generator.AuthProviders.Email as EmailProvider
|
||||
import qualified Wasp.Generator.AuthProviders.Local as LocalProvider
|
||||
@ -90,6 +91,7 @@ genProvidersIndex auth = return $ C.mkTmplFdWithData [relfile|src/auth/providers
|
||||
[OAuthProvider.providerId gitHubAuthProvider | AS.Auth.isGitHubAuthEnabled auth],
|
||||
[OAuthProvider.providerId googleAuthProvider | AS.Auth.isGoogleAuthEnabled auth],
|
||||
[OAuthProvider.providerId keycloakAuthProvider | AS.Auth.isKeycloakAuthEnabled auth],
|
||||
[OAuthProvider.providerId twitterAuthProvider | AS.Auth.isTwitterAuthEnabled auth],
|
||||
[LocalProvider.providerId localAuthProvider | AS.Auth.isUsernameAndPasswordAuthEnabled auth],
|
||||
[EmailProvider.providerId emailAuthProvider | AS.Auth.isEmailAuthEnabled auth]
|
||||
]
|
||||
|
@ -146,6 +146,7 @@ spec_Analyzer = do
|
||||
Auth.google = Nothing,
|
||||
Auth.keycloak = Nothing,
|
||||
Auth.gitHub = Nothing,
|
||||
Auth.twitter = Nothing,
|
||||
Auth.email = Nothing
|
||||
},
|
||||
Auth.onAuthFailedRedirectTo = "/",
|
||||
|
@ -117,6 +117,7 @@ spec_AppSpecValid = do
|
||||
AS.Auth.google = Nothing,
|
||||
AS.Auth.gitHub = Nothing,
|
||||
AS.Auth.keycloak = Nothing,
|
||||
AS.Auth.twitter = Nothing,
|
||||
AS.Auth.email = Nothing
|
||||
},
|
||||
AS.Auth.onAuthFailedRedirectTo = "/",
|
||||
@ -208,7 +209,7 @@ spec_AppSpecValid = do
|
||||
}
|
||||
|
||||
it "returns no error if app.auth is not set" $ do
|
||||
ASV.validateAppSpec (makeSpec (AS.Auth.AuthMethods {usernameAndPassword = Nothing, discord = Nothing, google = Nothing, keycloak = Nothing, gitHub = Nothing, email = Nothing}) validUserEntity) `shouldBe` []
|
||||
ASV.validateAppSpec (makeSpec (AS.Auth.AuthMethods {usernameAndPassword = Nothing, discord = Nothing, google = Nothing, keycloak = Nothing, gitHub = Nothing, twitter = Nothing, email = Nothing}) validUserEntity) `shouldBe` []
|
||||
|
||||
it "returns no error if app.auth is set and only one of UsernameAndPassword and Email is used" $ do
|
||||
ASV.validateAppSpec
|
||||
@ -223,13 +224,14 @@ spec_AppSpecValid = do
|
||||
google = Nothing,
|
||||
keycloak = Nothing,
|
||||
gitHub = Nothing,
|
||||
twitter = Nothing,
|
||||
email = Nothing
|
||||
}
|
||||
)
|
||||
validUserEntity
|
||||
)
|
||||
`shouldBe` []
|
||||
ASV.validateAppSpec (makeSpec (AS.Auth.AuthMethods {usernameAndPassword = Nothing, discord = Nothing, google = Nothing, keycloak = Nothing, gitHub = Nothing, email = Just emailAuthConfig}) validUserEntity) `shouldBe` []
|
||||
ASV.validateAppSpec (makeSpec (AS.Auth.AuthMethods {usernameAndPassword = Nothing, discord = Nothing, google = Nothing, keycloak = Nothing, gitHub = Nothing, twitter = Nothing, email = Just emailAuthConfig}) validUserEntity) `shouldBe` []
|
||||
|
||||
it "returns an error if app.auth is set and both UsernameAndPassword and Email are used" $ do
|
||||
ASV.validateAppSpec
|
||||
@ -244,6 +246,7 @@ spec_AppSpecValid = do
|
||||
google = Nothing,
|
||||
keycloak = Nothing,
|
||||
gitHub = Nothing,
|
||||
twitter = Nothing,
|
||||
email = Just emailAuthConfig
|
||||
}
|
||||
)
|
||||
@ -308,7 +311,7 @@ spec_AppSpecValid = do
|
||||
Just
|
||||
AS.Auth.Auth
|
||||
{ AS.Auth.methods =
|
||||
AS.Auth.AuthMethods {email = Just emailAuthConfig, usernameAndPassword = Nothing, discord = Nothing, google = Nothing, keycloak = Nothing, gitHub = Nothing},
|
||||
AS.Auth.AuthMethods {email = Just emailAuthConfig, usernameAndPassword = Nothing, discord = Nothing, google = Nothing, keycloak = Nothing, gitHub = Nothing, twitter = Nothing},
|
||||
AS.Auth.userEntity = AS.Core.Ref.Ref userEntityName,
|
||||
AS.Auth.externalAuthEntity = Nothing,
|
||||
AS.Auth.onAuthFailedRedirectTo = "/",
|
||||
|
@ -5,6 +5,7 @@
|
||||
--auth-pills-github: #f1f5f9;
|
||||
--auth-pills-google: #ecfccb;
|
||||
--auth-pills-keycloak: #d0ebf5;
|
||||
--auth-pills-twitter: #dbeafe;
|
||||
--auth-pills-username-and-pass: #fce7f3;
|
||||
}
|
||||
|
||||
@ -15,5 +16,6 @@
|
||||
--auth-pills-github: #334155;
|
||||
--auth-pills-google: #365314;
|
||||
--auth-pills-keycloak: #2d5866;
|
||||
--auth-pills-twittter: #0e7490;
|
||||
--auth-pills-username-and-pass: #831843;
|
||||
}
|
||||
|
@ -97,3 +97,16 @@ export function KeycloakPill() {
|
||||
</Pill>
|
||||
)
|
||||
}
|
||||
|
||||
export function TwitterPill() {
|
||||
return (
|
||||
<Pill
|
||||
style={{
|
||||
backgroundColor: 'var(--auth-pills-twitter)',
|
||||
}}
|
||||
linkToPage="/docs/auth/social-auth/twitter"
|
||||
>
|
||||
Twitter
|
||||
</Pill>
|
||||
)
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
title: Auth Hooks
|
||||
---
|
||||
|
||||
import { EmailPill, UsernameAndPasswordPill, GithubPill, GooglePill, KeycloakPill, DiscordPill } from "./Pills";
|
||||
import { EmailPill, UsernameAndPasswordPill, GithubPill, GooglePill, KeycloakPill, DiscordPill, TwitterPill } from "./Pills";
|
||||
import ImgWithCaption from '@site/blog/components/ImgWithCaption'
|
||||
import { ShowForTs } from '@site/src/components/TsJsHelpers'
|
||||
|
||||
@ -108,7 +108,7 @@ Wasp calls the `onBeforeSignup` hook before the user is created.
|
||||
|
||||
The `onBeforeSignup` hook can be useful if you want to reject a user based on some criteria before they sign up.
|
||||
|
||||
Works with <EmailPill /> <UsernameAndPasswordPill /> <DiscordPill /> <GithubPill /> <GooglePill /> <KeycloakPill />
|
||||
Works with <EmailPill /> <UsernameAndPasswordPill /> <DiscordPill /> <GithubPill /> <GooglePill /> <KeycloakPill /> <TwitterPill />
|
||||
|
||||
<Tabs groupId="js-ts">
|
||||
<TabItem value="js" label="JavaScript">
|
||||
@ -198,7 +198,7 @@ The `onAfterSignup` hook can be useful if you want to send the user a welcome em
|
||||
|
||||
Since the `onAfterSignup` hook receives the OAuth tokens, you can use this hook to store the OAuth access token and/or [refresh token](#refreshing-the-oauth-access-token) in your database.
|
||||
|
||||
Works with <EmailPill /> <UsernameAndPasswordPill /> <DiscordPill /> <GithubPill /> <GooglePill /> <KeycloakPill />
|
||||
Works with <EmailPill /> <UsernameAndPasswordPill /> <DiscordPill /> <GithubPill /> <GooglePill /> <KeycloakPill /> <TwitterPill />
|
||||
|
||||
<Tabs groupId="js-ts">
|
||||
<TabItem value="js" label="JavaScript">
|
||||
|
@ -26,6 +26,11 @@ export function SocialAuthGrid({
|
||||
description: 'Users sign in with their Discord account.',
|
||||
linkToDocs: '/docs/auth/social-auth/discord' + pagePart,
|
||||
},
|
||||
{
|
||||
title: 'Twitter',
|
||||
description: 'Users sign in with their Twitter account.',
|
||||
linkToDocs: '/docs/auth/social-auth/twitter' + pagePart,
|
||||
},
|
||||
]
|
||||
return (
|
||||
<>
|
||||
|
@ -2,7 +2,7 @@
|
||||
title: Auth UI
|
||||
---
|
||||
|
||||
import { EmailPill, UsernameAndPasswordPill, GithubPill, GooglePill, KeycloakPill, DiscordPill } from "./Pills";
|
||||
import { EmailPill, UsernameAndPasswordPill, GithubPill, GooglePill, KeycloakPill, DiscordPill, TwitterPill } from "./Pills";
|
||||
|
||||
To make using authentication in your app as easy as possible, Wasp generates the server-side code but also the client-side UI for you. It enables you to quickly get the login, signup, password reset and email verification flows in your app.
|
||||
|
||||
@ -102,15 +102,22 @@ Let's go through all of the available components and how to use them.
|
||||
|
||||
The following components are available for you to use in your app:
|
||||
|
||||
- [Login form](#login-form)
|
||||
- [Signup form](#signup-form)
|
||||
- [Forgot password form](#forgot-password-form)
|
||||
- [Reset password form](#reset-password-form)
|
||||
- [Verify email form](#verify-email-form)
|
||||
- [Overview](#overview)
|
||||
- [Auth Components](#auth-components)
|
||||
- [Login Form](#login-form)
|
||||
- [Signup Form](#signup-form)
|
||||
- [Forgot Password Form](#forgot-password-form)
|
||||
- [Reset Password Form](#reset-password-form)
|
||||
- [Verify Email Form](#verify-email-form)
|
||||
- [Customization 💅🏻](#customization-)
|
||||
- [1. Customizing the Colors](#1-customizing-the-colors)
|
||||
- [2. Using Your Logo](#2-using-your-logo)
|
||||
- [3. Social Buttons Layout](#3-social-buttons-layout)
|
||||
- [Let's Put Everything Together 🪄](#lets-put-everything-together-)
|
||||
|
||||
### Login Form
|
||||
|
||||
Used with <UsernameAndPasswordPill />, <EmailPill />, <GithubPill />, <GooglePill />, <KeycloakPill />, and <DiscordPill /> authentication.
|
||||
Used with <UsernameAndPasswordPill />, <EmailPill />, <GithubPill />, <GooglePill />, <KeycloakPill />, <DiscordPill />, and <TwitterPill /> authentication.
|
||||
|
||||
![Login form](/img/authui/login.png)
|
||||
|
||||
@ -165,7 +172,7 @@ It will automatically show the correct authentication providers based on your `m
|
||||
|
||||
### Signup Form
|
||||
|
||||
Used with <UsernameAndPasswordPill />, <EmailPill />, <GithubPill />, <GooglePill />, <KeycloakPill />, and <DiscordPill /> authentication.
|
||||
Used with <UsernameAndPasswordPill />, <EmailPill />, <GithubPill />, <GooglePill />, <KeycloakPill />, <DiscordPill />, and <TwitterPill /> authentication.
|
||||
|
||||
![Signup form](/img/authui/signup.png)
|
||||
|
||||
|
@ -73,6 +73,7 @@ module.exports = {
|
||||
'auth/social-auth/google',
|
||||
'auth/social-auth/keycloak',
|
||||
'auth/social-auth/discord',
|
||||
'auth/social-auth/twitter',
|
||||
],
|
||||
},
|
||||
'auth/entities/entities',
|
||||
|
@ -33,6 +33,11 @@ export function AuthMethodsGrid() {
|
||||
description: 'Users sign in with their Discord account',
|
||||
linkToDocs: '/docs/auth/social-auth/discord',
|
||||
},
|
||||
{
|
||||
title: 'Twitter',
|
||||
description: 'Users sign in with their Twitter account',
|
||||
linkToDocs: '/docs/auth/social-auth/twitter',
|
||||
},
|
||||
]
|
||||
return (
|
||||
<>
|
||||
|
Loading…
Reference in New Issue
Block a user