diff --git a/waspc/cli/src/Wasp/Cli/Command/Studio.hs b/waspc/cli/src/Wasp/Cli/Command/Studio.hs
index d031bc6fca..3ab15de559 100644
--- a/waspc/cli/src/Wasp/Cli/Command/Studio.hs
+++ b/waspc/cli/src/Wasp/Cli/Command/Studio.hs
@@ -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
],
diff --git a/waspc/data/Generator/templates/sdk/wasp/auth/forms/internal/common/LoginSignupForm.tsx b/waspc/data/Generator/templates/sdk/wasp/auth/forms/internal/common/LoginSignupForm.tsx
index 97e2e8ac1e..9b9bb00883 100644
--- a/waspc/data/Generator/templates/sdk/wasp/auth/forms/internal/common/LoginSignupForm.tsx
+++ b/waspc/data/Generator/templates/sdk/wasp/auth/forms/internal/common/LoginSignupForm.tsx
@@ -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 =}
{=/ enabledProviders.isGitHubAuthEnabled =}
+
+ {=# enabledProviders.isTwitterAuthEnabled =}
+
+ {=/ enabledProviders.isTwitterAuthEnabled =}
{=/ isSocialAuthEnabled =}
diff --git a/waspc/data/Generator/templates/sdk/wasp/auth/forms/internal/social/SocialIcons.tsx b/waspc/data/Generator/templates/sdk/wasp/auth/forms/internal/social/SocialIcons.tsx
index 0c8bf0eb25..4fcdbb24b7 100644
--- a/waspc/data/Generator/templates/sdk/wasp/auth/forms/internal/social/SocialIcons.tsx
+++ b/waspc/data/Generator/templates/sdk/wasp/auth/forms/internal/social/SocialIcons.tsx
@@ -65,3 +65,14 @@ export const Discord = () => (
)
+
+export const Twitter = () => (
+
+)
diff --git a/waspc/data/Generator/templates/sdk/wasp/auth/utils.ts b/waspc/data/Generator/templates/sdk/wasp/auth/utils.ts
index f5875de04c..0d0084af45 100644
--- a/waspc/data/Generator/templates/sdk/wasp/auth/utils.ts
+++ b/waspc/data/Generator/templates/sdk/wasp/auth/utils.ts
@@ -44,6 +44,7 @@ export type PossibleProviderData = {
google: OAuthProviderData;
keycloak: OAuthProviderData;
github: OAuthProviderData;
+ twitter: OAuthProviderData;
}
// PUBLIC API
diff --git a/waspc/data/Generator/templates/sdk/wasp/client/auth/index.ts b/waspc/data/Generator/templates/sdk/wasp/client/auth/index.ts
index a97231effd..73d8e1b2f1 100644
--- a/waspc/data/Generator/templates/sdk/wasp/client/auth/index.ts
+++ b/waspc/data/Generator/templates/sdk/wasp/client/auth/index.ts
@@ -18,6 +18,9 @@ export * from './keycloak'
{=# isGitHubAuthEnabled =}
export * from './github'
{=/ isGitHubAuthEnabled =}
+{=# isTwitterAuthEnabled =}
+export * from './twitter'
+{=/ isTwitterAuthEnabled =}
export {
default as useAuth,
getMe,
diff --git a/waspc/data/Generator/templates/sdk/wasp/client/auth/twitter.ts b/waspc/data/Generator/templates/sdk/wasp/client/auth/twitter.ts
new file mode 100644
index 0000000000..a1851c4f93
--- /dev/null
+++ b/waspc/data/Generator/templates/sdk/wasp/client/auth/twitter.ts
@@ -0,0 +1,2 @@
+// PUBLIC API
+export { signInUrl as twitterSignInUrl } from '../../auth/helpers/Twitter'
diff --git a/waspc/data/Generator/templates/sdk/wasp/client/auth/ui.ts b/waspc/data/Generator/templates/sdk/wasp/client/auth/ui.ts
index ea35d385a8..4268b2f6fc 100644
--- a/waspc/data/Generator/templates/sdk/wasp/client/auth/ui.ts
+++ b/waspc/data/Generator/templates/sdk/wasp/client/auth/ui.ts
@@ -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,
diff --git a/waspc/data/Generator/templates/sdk/wasp/server/auth/hooks.ts b/waspc/data/Generator/templates/sdk/wasp/server/auth/hooks.ts
index 6b5824dec8..2f8971c041 100644
--- a/waspc/data/Generator/templates/sdk/wasp/server/auth/hooks.ts
+++ b/waspc/data/Generator/templates/sdk/wasp/server/auth/hooks.ts
@@ -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
)
diff --git a/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/index.ts b/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/index.ts
index 7fc28ed4c2..629a55b098 100644
--- a/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/index.ts
+++ b/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/index.ts
@@ -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 {
diff --git a/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/providers/twitter.ts b/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/providers/twitter.ts
new file mode 100644
index 0000000000..8038567483
--- /dev/null
+++ b/waspc/data/Generator/templates/sdk/wasp/server/auth/oauth/providers/twitter.ts
@@ -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,
+});
diff --git a/waspc/data/Generator/templates/sdk/wasp/server/auth/user.ts b/waspc/data/Generator/templates/sdk/wasp/server/auth/user.ts
index 96ebf3c0fa..a85547e57d 100644
--- a/waspc/data/Generator/templates/sdk/wasp/server/auth/user.ts
+++ b/waspc/data/Generator/templates/sdk/wasp/server/auth/user.ts
@@ -49,6 +49,9 @@ export type AuthUserData = Omit> | null
{=/ enabledProviders.isGitHubAuthEnabled =}
+ {=# enabledProviders.isTwitterAuthEnabled =}
+ twitter: Expand> | 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,
diff --git a/waspc/data/Generator/templates/server/src/auth/providers/config/twitter.ts b/waspc/data/Generator/templates/server/src/auth/providers/config/twitter.ts
new file mode 100644
index 0000000000..540ec16a33
--- /dev/null
+++ b/waspc/data/Generator/templates/server/src/auth/providers/config/twitter.ts
@@ -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;
diff --git a/waspc/examples/todoApp/.env.server.example b/waspc/examples/todoApp/.env.server.example
index 76df894076..07d9b1b938 100644
--- a/waspc/examples/todoApp/.env.server.example
+++ b/waspc/examples/todoApp/.env.server.example
@@ -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'
diff --git a/waspc/examples/todoApp/main.wasp b/waspc/examples/todoApp/main.wasp
index 0043e397b4..78dc0c7f1d 100644
--- a/waspc/examples/todoApp/main.wasp
+++ b/waspc/examples/todoApp/main.wasp
@@ -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",
diff --git a/waspc/examples/todoApp/src/auth/twitter.ts b/waspc/examples/todoApp/src/auth/twitter.ts
new file mode 100644
index 0000000000..5b1e695df3
--- /dev/null
+++ b/waspc/examples/todoApp/src/auth/twitter.ts
@@ -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({})
diff --git a/waspc/src/Wasp/AppSpec/App/Auth.hs b/waspc/src/Wasp/AppSpec/App/Auth.hs
index 016dee74c1..aa59b80784 100644
--- a/waspc/src/Wasp/AppSpec/App/Auth.hs
+++ b/waspc/src/Wasp/AppSpec/App/Auth.hs
@@ -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
diff --git a/waspc/src/Wasp/Generator/AuthProviders.hs b/waspc/src/Wasp/Generator/AuthProviders.hs
index ef867c5662..91f3389e79 100644
--- a/waspc/src/Wasp/Generator/AuthProviders.hs
+++ b/waspc/src/Wasp/Generator/AuthProviders.hs
@@ -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
]
diff --git a/waspc/src/Wasp/Generator/SdkGenerator/Auth/AuthFormsG.hs b/waspc/src/Wasp/Generator/SdkGenerator/Auth/AuthFormsG.hs
index e24dd1b93b..1dc6206ab4 100644
--- a/waspc/src/Wasp/Generator/SdkGenerator/Auth/AuthFormsG.hs
+++ b/waspc/src/Wasp/Generator/SdkGenerator/Auth/AuthFormsG.hs
@@ -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
diff --git a/waspc/src/Wasp/Generator/SdkGenerator/Auth/OAuthAuthG.hs b/waspc/src/Wasp/Generator/SdkGenerator/Auth/OAuthAuthG.hs
index ecca128ed8..234749a27e 100644
--- a/waspc/src/Wasp/Generator/SdkGenerator/Auth/OAuthAuthG.hs
+++ b/waspc/src/Wasp/Generator/SdkGenerator/Auth/OAuthAuthG.hs
@@ -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 =
diff --git a/waspc/src/Wasp/Generator/SdkGenerator/Client/AuthG.hs b/waspc/src/Wasp/Generator/SdkGenerator/Client/AuthG.hs
index c182d49e80..e3c98133b9 100644
--- a/waspc/src/Wasp/Generator/SdkGenerator/Client/AuthG.hs
+++ b/waspc/src/Wasp/Generator/SdkGenerator/Client/AuthG.hs
@@ -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
diff --git a/waspc/src/Wasp/Generator/SdkGenerator/Server/OAuthG.hs b/waspc/src/Wasp/Generator/SdkGenerator/Server/OAuthG.hs
index fb533e22f1..09c8c5fb0c 100644
--- a/waspc/src/Wasp/Generator/SdkGenerator/Server/OAuthG.hs
+++ b/waspc/src/Wasp/Generator/SdkGenerator/Server/OAuthG.hs
@@ -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
diff --git a/waspc/src/Wasp/Generator/ServerGenerator/Auth/OAuthAuthG.hs b/waspc/src/Wasp/Generator/ServerGenerator/Auth/OAuthAuthG.hs
index a44da128ce..231b197f3d 100644
--- a/waspc/src/Wasp/Generator/ServerGenerator/Auth/OAuthAuthG.hs
+++ b/waspc/src/Wasp/Generator/ServerGenerator/Auth/OAuthAuthG.hs
@@ -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]
diff --git a/waspc/src/Wasp/Generator/ServerGenerator/AuthG.hs b/waspc/src/Wasp/Generator/ServerGenerator/AuthG.hs
index 8ae16e80ed..b6e37dc922 100644
--- a/waspc/src/Wasp/Generator/ServerGenerator/AuthG.hs
+++ b/waspc/src/Wasp/Generator/ServerGenerator/AuthG.hs
@@ -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]
]
diff --git a/waspc/test/AnalyzerTest.hs b/waspc/test/AnalyzerTest.hs
index 1a93c29f1a..7b63320f29 100644
--- a/waspc/test/AnalyzerTest.hs
+++ b/waspc/test/AnalyzerTest.hs
@@ -146,6 +146,7 @@ spec_Analyzer = do
Auth.google = Nothing,
Auth.keycloak = Nothing,
Auth.gitHub = Nothing,
+ Auth.twitter = Nothing,
Auth.email = Nothing
},
Auth.onAuthFailedRedirectTo = "/",
diff --git a/waspc/test/AppSpec/ValidTest.hs b/waspc/test/AppSpec/ValidTest.hs
index 7859f655ce..be3c35bdd1 100644
--- a/waspc/test/AppSpec/ValidTest.hs
+++ b/waspc/test/AppSpec/ValidTest.hs
@@ -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 = "/",
diff --git a/web/docs/auth/Pills.css b/web/docs/auth/Pills.css
index ac072b1195..a5e2633f06 100644
--- a/web/docs/auth/Pills.css
+++ b/web/docs/auth/Pills.css
@@ -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;
}
diff --git a/web/docs/auth/Pills.jsx b/web/docs/auth/Pills.jsx
index 2bd63708b0..ec1074cb6c 100644
--- a/web/docs/auth/Pills.jsx
+++ b/web/docs/auth/Pills.jsx
@@ -97,3 +97,16 @@ export function KeycloakPill() {
)
}
+
+export function TwitterPill() {
+ return (
+
+ Twitter
+
+ )
+}
diff --git a/web/docs/auth/auth-hooks.md b/web/docs/auth/auth-hooks.md
index 9106e988fd..f6d6a8a83c 100644
--- a/web/docs/auth/auth-hooks.md
+++ b/web/docs/auth/auth-hooks.md
@@ -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
+Works with
@@ -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
+Works with
diff --git a/web/docs/auth/social-auth/SocialAuthGrid.tsx b/web/docs/auth/social-auth/SocialAuthGrid.tsx
index 0cfaa05aab..8d4c76b9ec 100644
--- a/web/docs/auth/social-auth/SocialAuthGrid.tsx
+++ b/web/docs/auth/social-auth/SocialAuthGrid.tsx
@@ -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 (
<>
diff --git a/web/docs/auth/ui.md b/web/docs/auth/ui.md
index 07b6508a6f..a1fac10db1 100644
--- a/web/docs/auth/ui.md
+++ b/web/docs/auth/ui.md
@@ -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 , , , , , and authentication.
+Used with , , , , , , and 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 , , , , , and authentication.
+Used with , , , , , , and authentication.
![Signup form](/img/authui/signup.png)
diff --git a/web/sidebars.js b/web/sidebars.js
index 48dd6ea98f..d4e898f6ca 100644
--- a/web/sidebars.js
+++ b/web/sidebars.js
@@ -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',
diff --git a/web/src/components/AuthMethodsGrid.tsx b/web/src/components/AuthMethodsGrid.tsx
index ff1962c323..adcfdd8e30 100644
--- a/web/src/components/AuthMethodsGrid.tsx
+++ b/web/src/components/AuthMethodsGrid.tsx
@@ -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 (
<>