AuthProvidersFormValidation.tsx1780 lines · main
1import * as z from 'zod'
2
3import { NO_REQUIRED_CHARACTERS, urlRegex } from '@/components/interfaces/Auth/Auth.constants'
4import { ProjectAuthConfigData } from '@/data/auth/auth-config-query'
5import { DOCS_URL } from '@/lib/constants'
6
7const parseBase64URL = (b64url: string) => {
8 return atob(b64url.replace(/[-]/g, '+').replace(/[_]/g, '/'))
9}
10
11const JSON_SCHEMA_VERSION = 'http://json-schema.org/draft-07/schema#'
12
13const PROVIDER_EMAIL = {
14 $schema: JSON_SCHEMA_VERSION,
15 type: 'object',
16 title: 'Email',
17 link: `${DOCS_URL}/guides/auth/passwords`,
18 properties: {
19 EXTERNAL_EMAIL_ENABLED: {
20 title: 'Enable email provider',
21 description: 'Allow email-based sign up and log in for your application.',
22 type: 'boolean',
23 },
24 MAILER_SECURE_EMAIL_CHANGE_ENABLED: {
25 title: 'Secure email change',
26 description: `Users will be required to confirm any email change on both the old email address and new email address.
27 If disabled, only the new email is required to confirm.`,
28 type: 'boolean',
29 },
30 SECURITY_UPDATE_PASSWORD_REQUIRE_REAUTHENTICATION: {
31 title: 'Secure password change',
32 description: `Users will need to be recently logged in to change their password without requiring reauthentication. (A user is considered recently logged in if the session was created within the last 24 hours.)
33 If disabled, a user can change their password at any time.`,
34 type: 'boolean',
35 },
36 SECURITY_UPDATE_PASSWORD_REQUIRE_CURRENT_PASSWORD: {
37 title: 'Require current password when updating',
38 description: `Requires that the user supplies their current password when changing their password. [Learn more](${DOCS_URL}/guides/auth/password-security#require-current-password-when-changing).`,
39 type: 'boolean',
40 isPaid: false,
41 },
42 PASSWORD_HIBP_ENABLED: {
43 title: 'Prevent use of leaked passwords',
44 description: `Rejects the use of known or easy to guess passwords on sign up or password change. Powered by the HaveIBeenPwned.org Pwned Passwords API. [Learn more](${DOCS_URL}/guides/auth/password-security#password-strength-and-leaked-password-protection)`,
45 type: 'boolean',
46 entitlementKey: 'auth.password_hibp',
47 },
48 PASSWORD_MIN_LENGTH: {
49 title: 'Minimum password length',
50 type: 'number',
51 description:
52 'Passwords shorter than this value will be rejected as weak. Minimum 6 characters, though 8 or more is recommended.',
53 units: 'characters',
54 },
55 PASSWORD_REQUIRED_CHARACTERS: {
56 type: 'select',
57 title: 'Password requirements',
58 description: 'Passwords that do not have at least one of each will be rejected as weak.',
59 enum: [
60 {
61 label: 'No required characters (default)',
62 value: NO_REQUIRED_CHARACTERS,
63 },
64 {
65 label: 'Letters and digits',
66 value: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789',
67 },
68 {
69 label: 'Lowercase, uppercase letters and digits',
70 value: 'abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789',
71 },
72 {
73 label: 'Lowercase, uppercase letters, digits and symbols (recommended)',
74 value:
75 'abcdefghijklmnopqrstuvwxyz:ABCDEFGHIJKLMNOPQRSTUVWXYZ:0123456789:!@#$%^&*()_+-=[]{};\'\\\\:"|<>?,./`~',
76 },
77 ],
78 },
79 MAILER_OTP_EXP: {
80 title: 'Email OTP expiration',
81 type: 'number',
82 description: 'Duration before an email OTP / link expires.',
83 units: 'seconds',
84 },
85 MAILER_OTP_LENGTH: {
86 title: 'Email OTP length',
87 type: 'number',
88 description: 'Number of digits in the email OTP.',
89 units: 'digits',
90 },
91 },
92 validationSchema: z.discriminatedUnion('EXTERNAL_EMAIL_ENABLED', [
93 z.object({
94 EXTERNAL_EMAIL_ENABLED: z.literal(true),
95 MAILER_SECURE_EMAIL_CHANGE_ENABLED: z.boolean(),
96 SECURITY_UPDATE_PASSWORD_REQUIRE_REAUTHENTICATION: z.boolean(),
97 SECURITY_UPDATE_PASSWORD_REQUIRE_CURRENT_PASSWORD: z.boolean(),
98 PASSWORD_HIBP_ENABLED: z.boolean(),
99 MAILER_OTP_EXP: z.preprocess(
100 (val) => (val === '' || val == null ? undefined : val),
101 z.coerce
102 .number({ required_error: 'This is required', invalid_type_error: 'This is required' })
103 .min(0, 'Must be greater or equal to 0')
104 .max(86400, 'Must be no more than 86400')
105 ),
106 MAILER_OTP_LENGTH: z.preprocess(
107 (val) => (val === '' || val == null ? undefined : val),
108 z.coerce
109 .number({ required_error: 'This is required', invalid_type_error: 'This is required' })
110 .min(6, 'Must be greater or equal to 6')
111 .max(10, 'Must be no more than 10')
112 ),
113 PASSWORD_MIN_LENGTH: z.preprocess(
114 (val) => (val === '' || val == null ? undefined : val),
115 z.coerce
116 .number({ required_error: 'This is required', invalid_type_error: 'This is required' })
117 .min(6, 'Must be greater or equal to 6')
118 ),
119 PASSWORD_REQUIRED_CHARACTERS: z.string(),
120 }),
121 z.object({
122 EXTERNAL_EMAIL_ENABLED: z.literal(false),
123 MAILER_SECURE_EMAIL_CHANGE_ENABLED: z.boolean().optional(),
124 SECURITY_UPDATE_PASSWORD_REQUIRE_REAUTHENTICATION: z.boolean().optional(),
125 PASSWORD_HIBP_ENABLED: z.boolean().optional(),
126 SECURITY_UPDATE_PASSWORD_REQUIRE_CURRENT_PASSWORD: z.boolean().optional(),
127 MAILER_OTP_EXP: z.preprocess(
128 (val) => (val === '' || val == null ? undefined : val),
129 z.coerce.number().optional()
130 ),
131 MAILER_OTP_LENGTH: z.preprocess(
132 (val) => (val === '' || val == null ? undefined : val),
133 z.coerce.number().optional()
134 ),
135 PASSWORD_MIN_LENGTH: z.preprocess(
136 (val) => (val === '' || val == null ? undefined : val),
137 z.coerce.number().optional()
138 ),
139 PASSWORD_REQUIRED_CHARACTERS: z.string().optional(),
140 }),
141 ]),
142 misc: {
143 iconKey: 'email-icon2',
144 helper: `To complete setup, add this authorisation callback URL to your app's configuration in the Apple Developer Console.
145[Learn more](${DOCS_URL}/guides/auth/social-login/auth-apple#configure-your-services-id)`,
146 },
147}
148
149const smsProviderBaseSchema = z.object({
150 SMS_TEST_OTP: z
151 .string()
152 .trim()
153 .transform((value: string) => value.replace(/\s+/g, ''))
154 .refine((value) => {
155 // This ensure users can empty the SMS_TEST_OTP field
156 if (!value) return true
157 return /^\s*([0-9]{1,15}=[0-9]+)(\s*,\s*[0-9]{1,15}=[0-9]+)*\s*$/g.test(value)
158 }, 'Must be a comma-separated list of <phone number>=<OTP> pairs. Phone numbers should be in international format, without spaces, dashes or the + prefix. Example: 123456789=987654'),
159 SMS_TEST_OTP_VALID_UNTIL: z
160 .string()
161 .optional()
162 .refine((value) => {
163 if (!value) return true
164 const date = new Date(value)
165 return !isNaN(date.getTime())
166 }, 'Must be a valid date and time'),
167 SMS_AUTOCONFIRM: z.boolean().optional(),
168})
169
170const getSmsOtpPhoneProviderSchema = (optional = false) =>
171 z.object({
172 SMS_OTP_EXP: z.preprocess(
173 (val) => (val === '' || val == null ? undefined : val),
174 z.coerce
175 .number({ required_error: 'This is required', invalid_type_error: 'This is required' })
176 .min(0, 'Must be 0 or larger')
177 ),
178 SMS_OTP_LENGTH: z.preprocess(
179 (val) => (val === '' || val == null ? undefined : val),
180 z.coerce
181 .number({ required_error: 'This is required', invalid_type_error: 'This is required' })
182 .min(6, 'Must be 6 or larger')
183 ),
184 SMS_TEMPLATE: optional ? z.string() : z.string().min(1, 'SMS Template is required'),
185 })
186
187const getTwilioPhoneProviderSchema = (optional = false) =>
188 z.object({
189 SMS_TWILIO_ACCOUNT_SID: optional
190 ? z.string()
191 : z.string().min(1, 'Twilio Account SID is required'),
192 SMS_TWILIO_AUTH_TOKEN: optional
193 ? z.string()
194 : z.string().min(1, 'Twilio Auth Token is required'),
195 SMS_TWILIO_MESSAGE_SERVICE_SID: optional
196 ? z.string()
197 : z.string().min(1, 'Twilio Message Service SID is required'),
198 SMS_TWILIO_CONTENT_SID: z.string().optional(),
199 })
200
201const getTwilioVerifyPhoneProviderSchema = (optional = false) =>
202 z.object({
203 SMS_TWILIO_VERIFY_ACCOUNT_SID: optional
204 ? z.string()
205 : z.string().min(1, 'Twilio Verify Account SID is required'),
206 SMS_TWILIO_VERIFY_AUTH_TOKEN: optional
207 ? z.string()
208 : z.string().min(1, 'Twilio Verify Auth Token is required'),
209 SMS_TWILIO_VERIFY_MESSAGE_SERVICE_SID: optional
210 ? z.string()
211 : z.string().min(1, 'Twilio Verify Message Service SID is required'),
212 })
213
214const getMessagebirdPhoneProviderSchema = (optional = false) =>
215 z.object({
216 SMS_MESSAGEBIRD_ACCESS_KEY: optional
217 ? z.string()
218 : z.string().min(1, 'Messagebird Access Key is required'),
219 SMS_MESSAGEBIRD_ORIGINATOR: optional
220 ? z.string()
221 : z.string().min(1, 'Messagebird Originator is required'),
222 })
223
224const getTextlocalPhoneProviderSchema = (optional = false) =>
225 z.object({
226 SMS_TEXTLOCAL_API_KEY: optional
227 ? z.string()
228 : z.string().min(1, 'Textlocal API Key is required'),
229 SMS_TEXTLOCAL_SENDER: optional
230 ? z.string()
231 : z.string().min(1, 'Textlocal Sender ID is required'),
232 })
233
234const getVonagePhoneProviderSchema = (optional = false) =>
235 z.object({
236 SMS_VONAGE_API_KEY: optional ? z.string() : z.string().min(1, 'Vonage API Key is required'),
237 SMS_VONAGE_API_SECRET: optional
238 ? z.string()
239 : z.string().min(1, 'Vonage API Secret is required'),
240 SMS_VONAGE_FROM: optional ? z.string() : z.string().min(1, 'Vonage From is required'),
241 })
242
243const smsProviderEnabledSchema = z.object({
244 EXTERNAL_PHONE_ENABLED: z.literal(true),
245})
246
247const smsProviderDisabledSchema = z
248 .object({
249 EXTERNAL_PHONE_ENABLED: z.literal(false),
250 SMS_PROVIDER: z.string().optional(),
251 })
252 .merge(smsProviderBaseSchema.partial())
253 .merge(getSmsOtpPhoneProviderSchema(true).partial())
254 .merge(getTwilioPhoneProviderSchema(true).partial())
255 .merge(getTwilioVerifyPhoneProviderSchema(true).partial())
256 .merge(getMessagebirdPhoneProviderSchema(true).partial())
257 .merge(getVonagePhoneProviderSchema(true).partial())
258 .merge(getTextlocalPhoneProviderSchema(true).partial())
259
260// When the SMS hook is enabled, the validation for the SMS provider selected should be disabled
261// as the SMS hook will be used in place of the configured SMS provider
262const makeProviderOptionalWhenSMSHookEnabled = (
263 config: ProjectAuthConfigData,
264 getSchema: (optional?: boolean) => z.ZodObject<z.ZodRawShape>
265) => {
266 return config.HOOK_SEND_SMS_ENABLED ? getSchema(true).partial() : getSchema()
267}
268
269// getPhoneProviderValidationSchema generate the validation schema for the SMS providers
270// based on whether the SMS hook is enabled
271export const getPhoneProviderValidationSchema = (config: ProjectAuthConfigData) => {
272 const twilioSchema = makeProviderOptionalWhenSMSHookEnabled(config, getTwilioPhoneProviderSchema)
273 .merge(
274 z.object({
275 SMS_PROVIDER: z.literal('twilio'),
276 })
277 )
278 .merge(smsProviderBaseSchema)
279 .merge(getSmsOtpPhoneProviderSchema())
280 .merge(getTwilioVerifyPhoneProviderSchema(true).partial())
281 .merge(getMessagebirdPhoneProviderSchema(true).partial())
282 .merge(getVonagePhoneProviderSchema(true).partial())
283 .merge(getTextlocalPhoneProviderSchema(true).partial())
284
285 const twilioVerifySchema = makeProviderOptionalWhenSMSHookEnabled(
286 config,
287 getTwilioVerifyPhoneProviderSchema
288 )
289 .merge(
290 z.object({
291 SMS_PROVIDER: z.literal('twilio_verify'),
292 })
293 )
294 .merge(smsProviderBaseSchema)
295 .merge(getTwilioPhoneProviderSchema(true).partial())
296 .merge(getMessagebirdPhoneProviderSchema(true).partial())
297 .merge(getVonagePhoneProviderSchema(true).partial())
298 .merge(getTextlocalPhoneProviderSchema(true).partial())
299
300 const messagebirdSchema = makeProviderOptionalWhenSMSHookEnabled(
301 config,
302 getMessagebirdPhoneProviderSchema
303 )
304 .merge(
305 z.object({
306 SMS_PROVIDER: z.literal('messagebird'),
307 })
308 )
309 .merge(smsProviderBaseSchema)
310 .merge(getSmsOtpPhoneProviderSchema())
311 .merge(getTwilioPhoneProviderSchema(true).partial())
312 .merge(getTwilioVerifyPhoneProviderSchema(true).partial())
313 .merge(getVonagePhoneProviderSchema(true).partial())
314 .merge(getTextlocalPhoneProviderSchema(true).partial())
315
316 const vonageSchema = makeProviderOptionalWhenSMSHookEnabled(config, getVonagePhoneProviderSchema)
317 .merge(
318 z.object({
319 SMS_PROVIDER: z.literal('vonage'),
320 })
321 )
322 .merge(smsProviderBaseSchema)
323 .merge(getSmsOtpPhoneProviderSchema())
324 .merge(getTwilioPhoneProviderSchema(true).partial())
325 .merge(getTwilioVerifyPhoneProviderSchema(true).partial())
326 .merge(getMessagebirdPhoneProviderSchema(true).partial())
327 .merge(getTextlocalPhoneProviderSchema(true).partial())
328
329 const textlocalSchema = makeProviderOptionalWhenSMSHookEnabled(
330 config,
331 getTextlocalPhoneProviderSchema
332 )
333 .merge(
334 z.object({
335 SMS_PROVIDER: z.literal('textlocal'),
336 })
337 )
338 .merge(smsProviderBaseSchema)
339 .merge(getSmsOtpPhoneProviderSchema())
340 .merge(getTwilioPhoneProviderSchema(true).partial())
341 .merge(getTwilioVerifyPhoneProviderSchema(true).partial())
342 .merge(getMessagebirdPhoneProviderSchema(true).partial())
343 .merge(getVonagePhoneProviderSchema(true).partial())
344
345 const enabledSchema = z
346 .discriminatedUnion(
347 'SMS_PROVIDER',
348 [twilioSchema, twilioVerifySchema, messagebirdSchema, vonageSchema, textlocalSchema],
349 { message: 'Invalid SMS provider', invalid_type_error: 'Invalid SMS provider' }
350 )
351 .superRefine((values, ctx) => {
352 if (values.SMS_TEST_OTP && !values.SMS_TEST_OTP_VALID_UNTIL) {
353 ctx.addIssue({
354 code: 'custom',
355 message: 'You must provide a valid until date.',
356 path: ['SMS_TEST_OTP_VALID_UNTIL'],
357 })
358 }
359 })
360
361 return (
362 z
363 .discriminatedUnion('EXTERNAL_PHONE_ENABLED', [
364 smsProviderDisabledSchema,
365 // Passthrough only here to allow other values to propagate
366 // Must not be applied directly to smsProviderEnabledSchema to allow zod to transform the other values correctly
367 smsProviderEnabledSchema.passthrough(),
368 ])
369 // Trick: use superRefine to conditionally parse the enabled schema
370 .superRefine((values) => {
371 if (values.EXTERNAL_PHONE_ENABLED === true) {
372 return enabledSchema.parse(values)
373 }
374 })
375 // Trick: use transform to ensure the correct shape when EXTERNAL_PHONE_ENABLED is true.
376 // enabledSchema strips EXTERNAL_PHONE_ENABLED (not declared on its branches), so re-add it
377 // to keep the flag in the submitted payload.
378 .transform((values) => {
379 if (values.EXTERNAL_PHONE_ENABLED === true) {
380 return { ...enabledSchema.parse(values), EXTERNAL_PHONE_ENABLED: true as const }
381 }
382 return values
383 })
384 )
385}
386
387export const PROVIDER_PHONE = {
388 $schema: JSON_SCHEMA_VERSION,
389 type: 'object',
390 title: 'Phone',
391 link: `${DOCS_URL}/guides/auth/phone-login`,
392 properties: {
393 EXTERNAL_PHONE_ENABLED: {
394 title: 'Enable Phone provider',
395 description: 'This will enable phone based login for your application',
396 type: 'boolean',
397 },
398 SMS_PROVIDER: {
399 type: 'select',
400 title: 'SMS provider',
401 description: 'External provider that will handle sending SMS messages',
402 enum: [
403 { label: 'Twilio', value: 'twilio', icon: 'twilio-icon.svg' },
404 { label: 'Messagebird', value: 'messagebird', icon: 'messagebird-icon.svg' },
405 { label: 'Textlocal', value: 'textlocal', icon: 'textlocal-icon.png' },
406 { label: 'Vonage', value: 'vonage', icon: 'vonage-icon.svg' },
407 { label: 'Twilio Verify', value: 'twilio_verify', icon: 'twilio-icon.svg' },
408 ],
409 },
410
411 // Twilio
412 SMS_TWILIO_ACCOUNT_SID: {
413 type: 'string',
414 title: 'Twilio Account SID',
415 show: {
416 key: 'SMS_PROVIDER',
417 matches: ['twilio'],
418 },
419 },
420 SMS_TWILIO_AUTH_TOKEN: {
421 type: 'string',
422 title: 'Twilio Auth Token',
423 isSecret: true,
424 show: {
425 key: 'SMS_PROVIDER',
426 matches: ['twilio'],
427 },
428 },
429 SMS_TWILIO_MESSAGE_SERVICE_SID: {
430 type: 'string',
431 title: 'Twilio Message Service SID',
432 show: {
433 key: 'SMS_PROVIDER',
434 matches: ['twilio'],
435 },
436 },
437 SMS_TWILIO_CONTENT_SID: {
438 type: 'string',
439 title: 'Twilio Content SID (Optional, For WhatsApp Only)',
440 show: {
441 key: 'SMS_PROVIDER',
442 matches: ['twilio'],
443 },
444 },
445
446 // Twilio Verify
447 SMS_TWILIO_VERIFY_ACCOUNT_SID: {
448 type: 'string',
449 title: 'Twilio Account SID',
450 show: {
451 key: 'SMS_PROVIDER',
452 matches: ['twilio_verify'],
453 },
454 },
455 SMS_TWILIO_VERIFY_AUTH_TOKEN: {
456 type: 'string',
457 title: 'Twilio Auth Token',
458 isSecret: true,
459 show: {
460 key: 'SMS_PROVIDER',
461 matches: ['twilio_verify'],
462 },
463 },
464 SMS_TWILIO_VERIFY_MESSAGE_SERVICE_SID: {
465 type: 'string',
466 title: 'Twilio Verify Service SID',
467 show: {
468 key: 'SMS_PROVIDER',
469 matches: ['twilio_verify'],
470 },
471 },
472
473 // Messagebird
474 SMS_MESSAGEBIRD_ACCESS_KEY: {
475 type: 'string',
476 title: 'Messagebird Access Key',
477 show: {
478 key: 'SMS_PROVIDER',
479 matches: ['messagebird'],
480 },
481 },
482 SMS_MESSAGEBIRD_ORIGINATOR: {
483 type: 'string',
484 title: 'Messagebird Originator',
485 show: {
486 key: 'SMS_PROVIDER',
487 matches: ['messagebird'],
488 },
489 },
490
491 // Textloczl
492 SMS_TEXTLOCAL_API_KEY: {
493 type: 'string',
494 title: 'Textlocal API Key',
495 show: {
496 key: 'SMS_PROVIDER',
497 matches: ['textlocal'],
498 },
499 },
500 SMS_TEXTLOCAL_SENDER: {
501 type: 'string',
502 title: 'Textlocal Sender',
503 show: {
504 key: 'SMS_PROVIDER',
505 matches: ['textlocal'],
506 },
507 },
508
509 // Vonage
510 SMS_VONAGE_API_KEY: {
511 type: 'string',
512 title: 'Vonage API Key',
513 show: {
514 key: 'SMS_PROVIDER',
515 matches: ['vonage'],
516 },
517 },
518 SMS_VONAGE_API_SECRET: {
519 type: 'string',
520 title: 'Vonage API Secret',
521 show: {
522 key: 'SMS_PROVIDER',
523 matches: ['vonage'],
524 },
525 },
526 // [TODO] verify what this is?
527 SMS_VONAGE_FROM: {
528 type: 'string',
529 title: 'Vonage From',
530 show: {
531 key: 'SMS_PROVIDER',
532 matches: ['vonage'],
533 },
534 },
535
536 // SMS Confirm settings
537 SMS_AUTOCONFIRM: {
538 title: 'Enable phone confirmations',
539 type: 'boolean',
540 description: 'Users will need to confirm their phone number before signing in.',
541 },
542
543 SMS_OTP_EXP: {
544 title: 'SMS OTP Expiry',
545 type: 'number',
546 description: 'Duration before an SMS OTP expires',
547 units: 'seconds',
548 show: {
549 key: 'SMS_PROVIDER',
550 matches: ['twilio', 'messagebird', 'textlocal', 'vonage'],
551 },
552 },
553 SMS_OTP_LENGTH: {
554 title: 'SMS OTP Length',
555 type: 'number',
556 description: 'Number of digits in OTP',
557 units: 'digits',
558 show: {
559 key: 'SMS_PROVIDER',
560 matches: ['twilio', 'messagebird', 'textlocal', 'vonage'],
561 },
562 },
563 SMS_TEMPLATE: {
564 title: 'SMS Message',
565 type: 'multiline-string',
566 description: 'To format the OTP code use `{{ .Code }}`',
567 show: {
568 key: 'SMS_PROVIDER',
569 matches: ['twilio', 'messagebird', 'textlocal', 'vonage'],
570 },
571 },
572 SMS_TEST_OTP: {
573 type: 'string',
574 title: 'Test Phone Numbers and OTPs',
575 description:
576 'Register phone number and OTP combinations for testing as a comma separated list of <phone number>=<otp> pairs. Example: `18005550123=789012`',
577 },
578 SMS_TEST_OTP_VALID_UNTIL: {
579 type: 'datetime',
580 title: 'Test OTPs Valid Until',
581 description:
582 "Test phone number and OTP combinations won't be active past this date and time (local time zone).",
583 show: {
584 key: 'SMS_TEST_OTP',
585 },
586 },
587 },
588 validationSchema: null,
589 misc: {
590 iconKey: 'phone-icon4',
591 helper: `To complete setup, add this authorisation callback URL to your app's configuration in the Apple Developer Console.
592[Learn more](${DOCS_URL}/guides/auth/social-login/auth-apple#configure-your-services-id)`,
593 },
594}
595
596const EXTERNAL_PROVIDER_APPLE = {
597 $schema: JSON_SCHEMA_VERSION,
598 type: 'object',
599 title: 'Apple',
600 link: `${DOCS_URL}/guides/auth/social-login/auth-apple`,
601 properties: {
602 EXTERNAL_APPLE_ENABLED: {
603 title: 'Enable Sign in with Apple',
604 description:
605 'Enables Sign in with Apple on the web using OAuth or natively within iOS, macOS, watchOS or tvOS apps.',
606 type: 'boolean',
607 },
608 EXTERNAL_APPLE_CLIENT_ID: {
609 title: 'Client IDs',
610 description: `Comma separated list of allowed Apple app (Web, OAuth, iOS, macOS, watchOS, or tvOS) bundle IDs for native sign in, or service IDs for Sign in with Apple JS. [Learn more](https://developer.apple.com/documentation/signinwithapplejs)`,
611 type: 'string',
612 },
613 EXTERNAL_APPLE_SECRET: {
614 title: 'Secret Key (for OAuth)',
615 description: `Secret key used in the OAuth flow. [Learn more](${DOCS_URL}/guides/auth/social-login/auth-apple#generate-a-client_secret)`,
616 type: 'string',
617 isSecret: true,
618 },
619 EXTERNAL_APPLE_EMAIL_OPTIONAL: {
620 title: 'Allow users without an email',
621 description:
622 'Allows the user to successfully authenticate when the provider does not return an email address.',
623 type: 'boolean',
624 },
625 },
626 validationSchema: z.discriminatedUnion('EXTERNAL_APPLE_ENABLED', [
627 z.object({
628 EXTERNAL_APPLE_ENABLED: z.literal(false),
629 EXTERNAL_APPLE_SECRET: z.string().optional(),
630 EXTERNAL_APPLE_CLIENT_ID: z.string().optional(),
631 EXTERNAL_APPLE_EMAIL_OPTIONAL: z.boolean().optional(),
632 }),
633 z.object({
634 EXTERNAL_APPLE_ENABLED: z.literal(true),
635 EXTERNAL_APPLE_SECRET: z
636 .string()
637 .refine(
638 (value) => !value || /^([a-z0-9_-]+([.][a-z0-9_-]+){2})?$/i.test(value),
639 'Secret key should be a JWT.'
640 )
641 .refine((value) => {
642 if (!value) return true
643 try {
644 const parts = value.split('.').map((value) => parseBase64URL(value))
645 const header = JSON.parse(parts[0])
646 const body = JSON.parse(parts[1])
647
648 return (
649 typeof header === 'object' &&
650 typeof body === 'object' &&
651 header &&
652 body &&
653 header.alg === 'ES256' &&
654 body.aud === 'https://appleid.apple.com'
655 )
656 } catch (e: any) {
657 console.log(e)
658 return false
659 }
660 }, 'Secret key is not a correctly generated JWT.')
661 .refine((value) => {
662 if (!value) return true
663 try {
664 const parts = value.split('.').map((value) => parseBase64URL(value))
665 const body = JSON.parse(parts[1])
666 return Date.now() < body.exp * 1000 - 7 * 24 * 60 * 60 * 1000
667 } catch (e: any) {
668 console.log(e)
669 return false
670 }
671 }, 'Secret key expires in less than 7 days!'),
672 EXTERNAL_APPLE_CLIENT_ID: z
673 .string()
674 .min(1, 'At least one Client ID is required when Apple sign-in is enabled.')
675 .regex(/^\S+$/, 'Client IDs should not contain spaces.')
676 .regex(
677 /^([a-z0-9-]+(\.[a-z0-9-]+)*(,[a-z0-9-]+(\.[a-z0-9-]+)*)*)$/i,
678 'Invalid characters. Each ID should follow a reverse-domain style string (e.g. com.example.app). Use commas to separate multiple IDs.'
679 ),
680 EXTERNAL_APPLE_EMAIL_OPTIONAL: z.boolean().optional(),
681 }),
682 ]),
683 misc: {
684 iconKey: 'apple-icon',
685 requiresRedirect: true,
686 helper: `Register this callback URL when using Sign in with Apple on the web in the Apple Developer Center.
687[Learn more](${DOCS_URL}/guides/auth/social-login/auth-apple#configure-your-services-id)`,
688 alert: {
689 title: `Apple OAuth secret keys expire every 6 months`,
690 description: `A new secret should be generated every 6 months, otherwise users on the web will not be able to sign in.`,
691 },
692 },
693}
694
695const EXTERNAL_PROVIDER_AZURE = {
696 $schema: JSON_SCHEMA_VERSION,
697 type: 'object',
698 title: 'Azure',
699 link: `${DOCS_URL}/guides/auth/social-login/auth-azure`,
700 properties: {
701 EXTERNAL_AZURE_ENABLED: {
702 title: 'Azure enabled',
703 type: 'boolean',
704 },
705 EXTERNAL_AZURE_CLIENT_ID: {
706 // [TODO] Change docs
707 title: 'Application (client) ID',
708 type: 'string',
709 },
710 EXTERNAL_AZURE_SECRET: {
711 // [TODO] Change docs
712 title: 'Secret Value',
713 description: `Enter the data from Value, not the Secret ID. [Learn more](${DOCS_URL}/guides/auth/social-login/auth-azure#obtain-a-secret-id)`,
714 type: 'string',
715 isSecret: true,
716 },
717 EXTERNAL_AZURE_URL: {
718 // [TODO] Change docs
719 title: 'Azure Tenant URL',
720 descriptionOptional: 'Optional',
721 type: 'string',
722 },
723 EXTERNAL_AZURE_EMAIL_OPTIONAL: {
724 title: 'Allow users without an email',
725 description:
726 'Allows the user to successfully authenticate when the provider does not return an email address.',
727 type: 'boolean',
728 },
729 },
730 validationSchema: z.discriminatedUnion('EXTERNAL_AZURE_ENABLED', [
731 z.object({
732 EXTERNAL_AZURE_ENABLED: z.literal(true),
733 EXTERNAL_AZURE_CLIENT_ID: z.string().min(1, 'Application (client) ID is required'),
734 EXTERNAL_AZURE_SECRET: z.string().min(1, 'Secret Value is required'),
735 EXTERNAL_AZURE_URL: z
736 .string()
737 .refine((value) => !value || urlRegex().test(value), 'Must be a valid URL'),
738 EXTERNAL_AZURE_EMAIL_OPTIONAL: z.boolean().optional(),
739 }),
740 z.object({
741 EXTERNAL_AZURE_ENABLED: z.literal(false),
742 EXTERNAL_AZURE_CLIENT_ID: z.string().optional(),
743 EXTERNAL_AZURE_SECRET: z.string().optional(),
744 EXTERNAL_AZURE_URL: z.string().optional(),
745 EXTERNAL_AZURE_EMAIL_OPTIONAL: z.boolean().optional(),
746 }),
747 ]),
748 misc: {
749 iconKey: 'microsoft-icon',
750 requiresRedirect: true,
751 },
752}
753
754const EXTERNAL_PROVIDER_BITBUCKET = {
755 $schema: JSON_SCHEMA_VERSION,
756 type: 'object',
757 title: 'Bitbucket',
758 link: `${DOCS_URL}/guides/auth/social-login/auth-bitbucket`,
759 properties: {
760 EXTERNAL_BITBUCKET_ENABLED: {
761 title: 'Bitbucket enabled',
762 type: 'boolean',
763 },
764 EXTERNAL_BITBUCKET_CLIENT_ID: {
765 title: 'Key',
766 type: 'string',
767 },
768 EXTERNAL_BITBUCKET_SECRET: {
769 title: 'Secret',
770 type: 'string',
771 isSecret: true,
772 },
773 EXTERNAL_BITBUCKET_EMAIL_OPTIONAL: {
774 title: 'Allow users without an email',
775 description:
776 'Allows the user to successfully authenticate when the provider does not return an email address.',
777 type: 'boolean',
778 },
779 },
780 validationSchema: z.discriminatedUnion('EXTERNAL_BITBUCKET_ENABLED', [
781 z.object({
782 EXTERNAL_BITBUCKET_ENABLED: z.literal(true),
783 EXTERNAL_BITBUCKET_CLIENT_ID: z.string().min(1, 'Key is required'),
784 EXTERNAL_BITBUCKET_SECRET: z.string().min(1, 'Secret is required'),
785 EXTERNAL_BITBUCKET_EMAIL_OPTIONAL: z.boolean().optional(),
786 }),
787 z.object({
788 EXTERNAL_BITBUCKET_ENABLED: z.literal(false),
789 EXTERNAL_BITBUCKET_CLIENT_ID: z.string().optional(),
790 EXTERNAL_BITBUCKET_SECRET: z.string().optional(),
791 EXTERNAL_BITBUCKET_EMAIL_OPTIONAL: z.boolean().optional(),
792 }),
793 ]),
794 misc: {
795 iconKey: 'bitbucket-icon',
796 requiresRedirect: true,
797 },
798}
799
800const EXTERNAL_PROVIDER_DISCORD = {
801 $schema: JSON_SCHEMA_VERSION,
802 type: 'object',
803 title: 'Discord',
804 link: `${DOCS_URL}/guides/auth/social-login/auth-discord?`,
805 properties: {
806 EXTERNAL_DISCORD_ENABLED: {
807 title: 'Discord enabled',
808 type: 'boolean',
809 },
810 EXTERNAL_DISCORD_CLIENT_ID: {
811 title: 'Client ID',
812 type: 'string',
813 },
814 EXTERNAL_DISCORD_SECRET: {
815 title: 'Client Secret',
816 type: 'string',
817 isSecret: true,
818 },
819 EXTERNAL_DISCORD_EMAIL_OPTIONAL: {
820 title: 'Allow users without an email',
821 description:
822 'Allows the user to successfully authenticate when the provider does not return an email address.',
823 type: 'boolean',
824 },
825 },
826 validationSchema: z.discriminatedUnion('EXTERNAL_DISCORD_ENABLED', [
827 z.object({
828 EXTERNAL_DISCORD_ENABLED: z.literal(true),
829 EXTERNAL_DISCORD_CLIENT_ID: z.string().min(1, 'Client ID is required'),
830 EXTERNAL_DISCORD_SECRET: z.string().min(1, 'Client Secret is required'),
831 EXTERNAL_DISCORD_EMAIL_OPTIONAL: z.boolean().optional(),
832 }),
833 z.object({
834 EXTERNAL_DISCORD_ENABLED: z.literal(false),
835 EXTERNAL_DISCORD_CLIENT_ID: z.string().optional(),
836 EXTERNAL_DISCORD_SECRET: z.string().optional(),
837 EXTERNAL_DISCORD_EMAIL_OPTIONAL: z.boolean().optional(),
838 }),
839 ]),
840 misc: {
841 iconKey: 'discord-icon',
842 requiresRedirect: true,
843 },
844}
845
846const EXTERNAL_PROVIDER_FACEBOOK = {
847 $schema: JSON_SCHEMA_VERSION,
848 type: 'object',
849 title: 'Facebook',
850 link: `${DOCS_URL}/guides/auth/social-login/auth-facebook`,
851 properties: {
852 EXTERNAL_FACEBOOK_ENABLED: {
853 title: 'Facebook enabled',
854 type: 'boolean',
855 },
856 EXTERNAL_FACEBOOK_CLIENT_ID: {
857 title: 'Facebook client ID',
858 type: 'string',
859 },
860 EXTERNAL_FACEBOOK_SECRET: {
861 title: 'Facebook secret',
862 type: 'string',
863 isSecret: true,
864 },
865 EXTERNAL_FACEBOOK_EMAIL_OPTIONAL: {
866 title: 'Allow users without an email',
867 description:
868 'Allows the user to successfully authenticate when the provider does not return an email address.',
869 type: 'boolean',
870 },
871 },
872 validationSchema: z.discriminatedUnion('EXTERNAL_FACEBOOK_ENABLED', [
873 z.object({
874 EXTERNAL_FACEBOOK_ENABLED: z.literal(true),
875 EXTERNAL_FACEBOOK_CLIENT_ID: z.string().min(1, '"Facebook client ID" is required'),
876 EXTERNAL_FACEBOOK_SECRET: z.string().min(1, '"Facebook secret" is required'),
877 EXTERNAL_FACEBOOK_EMAIL_OPTIONAL: z.boolean().optional(),
878 }),
879 z.object({
880 EXTERNAL_FACEBOOK_ENABLED: z.literal(false),
881 EXTERNAL_FACEBOOK_CLIENT_ID: z.string().optional(),
882 EXTERNAL_FACEBOOK_SECRET: z.string().optional(),
883 EXTERNAL_FACEBOOK_EMAIL_OPTIONAL: z.boolean().optional(),
884 }),
885 ]),
886 misc: {
887 iconKey: 'facebook-icon',
888 requiresRedirect: true,
889 },
890}
891
892const EXTERNAL_PROVIDER_FIGMA = {
893 $schema: JSON_SCHEMA_VERSION,
894 type: 'object',
895 title: 'Figma',
896 link: `${DOCS_URL}/guides/auth/social-login/auth-figma`,
897 properties: {
898 EXTERNAL_FIGMA_ENABLED: {
899 title: 'Figma enabled',
900 type: 'boolean',
901 },
902 EXTERNAL_FIGMA_CLIENT_ID: {
903 title: 'Client ID',
904 type: 'string',
905 },
906 EXTERNAL_FIGMA_SECRET: {
907 title: 'Client Secret',
908 type: 'string',
909 isSecret: true,
910 },
911 EXTERNAL_FIGMA_EMAIL_OPTIONAL: {
912 title: 'Allow users without an email',
913 description:
914 'Allows the user to successfully authenticate when the provider does not return an email address.',
915 type: 'boolean',
916 },
917 },
918 validationSchema: z.discriminatedUnion('EXTERNAL_FIGMA_ENABLED', [
919 z.object({
920 EXTERNAL_FIGMA_ENABLED: z.literal(true),
921 EXTERNAL_FIGMA_CLIENT_ID: z.string().min(1, 'Client ID is required'),
922 EXTERNAL_FIGMA_SECRET: z.string().min(1, 'Client Secret is required'),
923 EXTERNAL_FIGMA_EMAIL_OPTIONAL: z.boolean().optional(),
924 }),
925 z.object({
926 EXTERNAL_FIGMA_ENABLED: z.literal(false),
927 EXTERNAL_FIGMA_CLIENT_ID: z.string().optional(),
928 EXTERNAL_FIGMA_SECRET: z.string().optional(),
929 EXTERNAL_FIGMA_EMAIL_OPTIONAL: z.boolean().optional(),
930 }),
931 ]),
932 misc: {
933 iconKey: 'figma-icon',
934 requiresRedirect: true,
935 },
936}
937
938const EXTERNAL_PROVIDER_GITHUB = {
939 $schema: JSON_SCHEMA_VERSION,
940 type: 'object',
941 title: 'GitHub',
942 link: `${DOCS_URL}/guides/auth/social-login/auth-github`,
943 properties: {
944 EXTERNAL_GITHUB_ENABLED: {
945 title: 'GitHub enabled',
946 type: 'boolean',
947 },
948 EXTERNAL_GITHUB_CLIENT_ID: {
949 title: 'Client ID',
950 type: 'string',
951 },
952 EXTERNAL_GITHUB_SECRET: {
953 title: 'Client Secret',
954 type: 'string',
955 isSecret: true,
956 },
957 EXTERNAL_GITHUB_EMAIL_OPTIONAL: {
958 title: 'Allow users without an email',
959 description:
960 'Allows the user to successfully authenticate when the provider does not return an email address.',
961 type: 'boolean',
962 },
963 },
964 validationSchema: z.discriminatedUnion('EXTERNAL_GITHUB_ENABLED', [
965 z.object({
966 EXTERNAL_GITHUB_ENABLED: z.literal(true),
967 EXTERNAL_GITHUB_CLIENT_ID: z.string().min(1, 'Client ID is required'),
968 EXTERNAL_GITHUB_SECRET: z.string().min(1, 'Client Secret is required'),
969 EXTERNAL_GITHUB_EMAIL_OPTIONAL: z.boolean().optional(),
970 }),
971 z.object({
972 EXTERNAL_GITHUB_ENABLED: z.literal(false),
973 EXTERNAL_GITHUB_CLIENT_ID: z.string().optional(),
974 EXTERNAL_GITHUB_SECRET: z.string().optional(),
975 EXTERNAL_GITHUB_EMAIL_OPTIONAL: z.boolean().optional(),
976 }),
977 ]),
978 misc: {
979 iconKey: 'github-icon',
980 requiresRedirect: true,
981 },
982}
983
984const EXTERNAL_PROVIDER_GITLAB = {
985 $schema: JSON_SCHEMA_VERSION,
986 type: 'object',
987 title: 'GitLab',
988 link: `${DOCS_URL}/guides/auth/social-login/auth-gitlab`,
989 properties: {
990 EXTERNAL_GITLAB_ENABLED: {
991 title: 'GitLab enabled',
992 type: 'boolean',
993 },
994 // [TODO] Update docs
995 EXTERNAL_GITLAB_CLIENT_ID: {
996 title: 'Application ID',
997 type: 'string',
998 },
999 // [TODO] Update docs
1000 EXTERNAL_GITLAB_SECRET: {
1001 title: 'Secret',
1002 type: 'string',
1003 isSecret: true,
1004 },
1005 EXTERNAL_GITLAB_URL: {
1006 title: 'Self Hosted GitLab URL',
1007 descriptionOptional: 'Optional',
1008 type: 'string',
1009 },
1010 EXTERNAL_GITLAB_EMAIL_OPTIONAL: {
1011 title: 'Allow users without an email',
1012 description:
1013 'Allows the user to successfully authenticate when the provider does not return an email address.',
1014 type: 'boolean',
1015 },
1016 },
1017 validationSchema: z.discriminatedUnion('EXTERNAL_GITLAB_ENABLED', [
1018 z.object({
1019 EXTERNAL_GITLAB_ENABLED: z.literal(false),
1020 EXTERNAL_GITLAB_CLIENT_ID: z.string().optional(),
1021 EXTERNAL_GITLAB_SECRET: z.string().optional(),
1022 EXTERNAL_GITLAB_URL: z.string().optional(),
1023 EXTERNAL_GITLAB_EMAIL_OPTIONAL: z.boolean().optional(),
1024 }),
1025 z.object({
1026 EXTERNAL_GITLAB_ENABLED: z.literal(true),
1027 EXTERNAL_GITLAB_CLIENT_ID: z.string().min(1, 'Client ID is required'),
1028 EXTERNAL_GITLAB_SECRET: z.string().min(1, 'Client Secret is required'),
1029 EXTERNAL_GITLAB_URL: z
1030 .string()
1031 .refine((value) => !value || urlRegex().test(value), 'Must be a valid URL')
1032 .optional(),
1033 EXTERNAL_GITLAB_EMAIL_OPTIONAL: z.boolean().optional(),
1034 }),
1035 ]),
1036 misc: {
1037 iconKey: 'gitlab-icon',
1038 requiresRedirect: true,
1039 },
1040}
1041
1042const EXTERNAL_PROVIDER_GOOGLE = {
1043 $schema: JSON_SCHEMA_VERSION,
1044 type: 'object',
1045 title: 'Google',
1046 link: `${DOCS_URL}/guides/auth/social-login/auth-google`,
1047 properties: {
1048 EXTERNAL_GOOGLE_ENABLED: {
1049 title: 'Enable Sign in with Google',
1050 description:
1051 'Enables Sign in with Google on the web using OAuth or One Tap, or in Android apps or Chrome extensions.',
1052 type: 'boolean',
1053 },
1054 EXTERNAL_GOOGLE_CLIENT_ID: {
1055 title: 'Client IDs',
1056 description:
1057 'Comma-separated list of client IDs for Web, OAuth, Android apps, One Tap, and Chrome extensions.',
1058 type: 'string',
1059 },
1060 EXTERNAL_GOOGLE_SECRET: {
1061 title: 'Client Secret (for OAuth)',
1062 description: 'Client Secret to use with the OAuth flow on the web.',
1063 type: 'string',
1064 isSecret: true,
1065 },
1066 EXTERNAL_GOOGLE_SKIP_NONCE_CHECK: {
1067 title: 'Skip nonce checks',
1068 description:
1069 "Allows ID tokens with any nonce to be accepted, which is less secure. Useful in situations where you don't have access to the nonce used to issue the ID token, such as with iOS.",
1070 type: 'boolean',
1071 },
1072 EXTERNAL_GOOGLE_EMAIL_OPTIONAL: {
1073 title: 'Allow users without an email',
1074 description:
1075 'Allows the user to successfully authenticate when the provider does not return an email address.',
1076 type: 'boolean',
1077 },
1078 },
1079 validationSchema: z.discriminatedUnion('EXTERNAL_GOOGLE_ENABLED', [
1080 z.object({
1081 EXTERNAL_GOOGLE_ENABLED: z.literal(false),
1082 EXTERNAL_GOOGLE_CLIENT_ID: z.string().optional(),
1083 EXTERNAL_GOOGLE_SECRET: z.string().optional(),
1084 EXTERNAL_GOOGLE_SKIP_NONCE_CHECK: z.boolean().optional(),
1085 EXTERNAL_GOOGLE_EMAIL_OPTIONAL: z.boolean().optional(),
1086 }),
1087 z.object({
1088 EXTERNAL_GOOGLE_ENABLED: z.literal(true),
1089 EXTERNAL_GOOGLE_CLIENT_ID: z
1090 .string()
1091 .min(1, 'At least one Client ID is required when Google sign-in is enabled.')
1092 .regex(/^\S+$/, 'Client IDs should not contain spaces.')
1093 .regex(
1094 /^([a-z0-9-]+\.[a-z0-9-]+(\.[a-z0-9-]+)*(,[a-z0-9-]+\.[a-z0-9-]+(\.[a-z0-9-]+)*)*)$/i,
1095 'Invalid characters. Google Client IDs should be a comma-separated list of domain-like strings.'
1096 ),
1097 EXTERNAL_GOOGLE_SECRET: z.string().optional(),
1098 EXTERNAL_GOOGLE_SKIP_NONCE_CHECK: z.boolean().optional(),
1099 EXTERNAL_GOOGLE_EMAIL_OPTIONAL: z.boolean().optional(),
1100 }),
1101 ]),
1102 misc: {
1103 iconKey: 'google-icon',
1104 requiresRedirect: true,
1105 helper: `Register this callback URL when using Sign-in with Google on the web using OAuth.
1106[Learn more](${DOCS_URL}/guides/auth/social-login/auth-google#configure-your-services-id)`,
1107 },
1108}
1109
1110const EXTERNAL_PROVIDER_KAKAO = {
1111 $schema: JSON_SCHEMA_VERSION,
1112 type: 'object',
1113 title: 'Kakao',
1114 link: `${DOCS_URL}/guides/auth/social-login/auth-kakao`,
1115 properties: {
1116 EXTERNAL_KAKAO_ENABLED: {
1117 title: 'Kakao enabled',
1118 type: 'boolean',
1119 },
1120 // [TODO] Update docs
1121 EXTERNAL_KAKAO_CLIENT_ID: {
1122 title: 'REST API Key',
1123 type: 'string',
1124 },
1125 // [TODO] Update docs
1126 EXTERNAL_KAKAO_SECRET: {
1127 title: 'Client Secret Code',
1128 type: 'string',
1129 isSecret: true,
1130 },
1131 EXTERNAL_KAKAO_EMAIL_OPTIONAL: {
1132 title: 'Allow users without an email',
1133 description:
1134 'Allows the user to successfully authenticate when the provider does not return an email address.',
1135 type: 'boolean',
1136 },
1137 },
1138 validationSchema: z.discriminatedUnion('EXTERNAL_KAKAO_ENABLED', [
1139 z.object({
1140 EXTERNAL_KAKAO_ENABLED: z.literal(false),
1141 EXTERNAL_KAKAO_CLIENT_ID: z.string().optional(),
1142 EXTERNAL_KAKAO_SECRET: z.string().optional(),
1143 EXTERNAL_KAKAO_EMAIL_OPTIONAL: z.boolean().optional(),
1144 }),
1145 z.object({
1146 EXTERNAL_KAKAO_ENABLED: z.literal(true),
1147 EXTERNAL_KAKAO_CLIENT_ID: z.string().min(1, 'REST API Key is required'),
1148 EXTERNAL_KAKAO_SECRET: z.string().min(1, 'Client Secret Code is required'),
1149 EXTERNAL_KAKAO_EMAIL_OPTIONAL: z.boolean().optional(),
1150 }),
1151 ]),
1152 misc: {
1153 iconKey: 'kakao-icon',
1154 requiresRedirect: true,
1155 },
1156}
1157
1158// [TODO]: clarify the EXTERNAL_KEYCLOAK_URL property
1159const EXTERNAL_PROVIDER_KEYCLOAK = {
1160 $schema: JSON_SCHEMA_VERSION,
1161 type: 'object',
1162 title: 'KeyCloak',
1163 link: `${DOCS_URL}/guides/auth/social-login/auth-keycloak`,
1164 properties: {
1165 EXTERNAL_KEYCLOAK_ENABLED: {
1166 title: 'Keycloak enabled',
1167 type: 'boolean',
1168 },
1169 EXTERNAL_KEYCLOAK_CLIENT_ID: {
1170 title: 'Client ID',
1171 type: 'string',
1172 },
1173 EXTERNAL_KEYCLOAK_SECRET: {
1174 title: 'Secret',
1175 type: 'string',
1176 isSecret: true,
1177 },
1178 EXTERNAL_KEYCLOAK_URL: {
1179 title: 'Realm URL',
1180 description: '',
1181 type: 'string',
1182 },
1183 EXTERNAL_KEYCLOAK_EMAIL_OPTIONAL: {
1184 title: 'Allow users without an email',
1185 description:
1186 'Allows the user to successfully authenticate when the provider does not return an email address.',
1187 type: 'boolean',
1188 },
1189 },
1190 validationSchema: z.discriminatedUnion('EXTERNAL_KEYCLOAK_ENABLED', [
1191 z.object({
1192 EXTERNAL_KEYCLOAK_ENABLED: z.literal(false),
1193 EXTERNAL_KEYCLOAK_CLIENT_ID: z.string().optional(),
1194 EXTERNAL_KEYCLOAK_SECRET: z.string().optional(),
1195 EXTERNAL_KEYCLOAK_URL: z.string().optional(),
1196 EXTERNAL_KEYCLOAK_EMAIL_OPTIONAL: z.boolean().optional(),
1197 }),
1198 z.object({
1199 EXTERNAL_KEYCLOAK_ENABLED: z.literal(true),
1200 EXTERNAL_KEYCLOAK_CLIENT_ID: z.string().min(1, 'Client ID is required'),
1201 EXTERNAL_KEYCLOAK_SECRET: z.string().min(1, 'Client secret is required'),
1202 EXTERNAL_KEYCLOAK_URL: z
1203 .string()
1204 .min(1, 'Realm URL is required')
1205 .regex(urlRegex(), 'Must be a valid URL'),
1206 EXTERNAL_KEYCLOAK_EMAIL_OPTIONAL: z.boolean().optional(),
1207 }),
1208 ]),
1209 misc: {
1210 iconKey: 'keycloak-icon',
1211 requiresRedirect: true,
1212 },
1213}
1214
1215const EXTERNAL_PROVIDER_LINKEDIN_OIDC = {
1216 $schema: JSON_SCHEMA_VERSION,
1217 type: 'object',
1218 key: 'linkedin_oidc',
1219 title: 'LinkedIn (OIDC)',
1220 link: `${DOCS_URL}/guides/auth/social-login/auth-linkedin`,
1221 properties: {
1222 EXTERNAL_LINKEDIN_OIDC_ENABLED: {
1223 title: 'LinkedIn enabled',
1224 type: 'boolean',
1225 },
1226 EXTERNAL_LINKEDIN_OIDC_CLIENT_ID: {
1227 title: 'API Key',
1228 type: 'string',
1229 },
1230 EXTERNAL_LINKEDIN_OIDC_SECRET: {
1231 title: 'API Secret Key',
1232 type: 'string',
1233 isSecret: true,
1234 },
1235 EXTERNAL_LINKEDIN_OIDC_EMAIL_OPTIONAL: {
1236 title: 'Allow users without an email',
1237 description:
1238 'Allows the user to successfully authenticate when the provider does not return an email address.',
1239 type: 'boolean',
1240 },
1241 },
1242 validationSchema: z.discriminatedUnion('EXTERNAL_LINKEDIN_OIDC_ENABLED', [
1243 z.object({
1244 EXTERNAL_LINKEDIN_OIDC_ENABLED: z.literal(false),
1245 EXTERNAL_LINKEDIN_OIDC_CLIENT_ID: z.string().optional(),
1246 EXTERNAL_LINKEDIN_OIDC_SECRET: z.string().optional(),
1247 EXTERNAL_LINKEDIN_OIDC_EMAIL_OPTIONAL: z.boolean().optional(),
1248 }),
1249 z.object({
1250 EXTERNAL_LINKEDIN_OIDC_ENABLED: z.literal(true),
1251 EXTERNAL_LINKEDIN_OIDC_CLIENT_ID: z.string().min(1, 'API Key is required'),
1252 EXTERNAL_LINKEDIN_OIDC_SECRET: z.string().min(1, 'API Secret Key is required'),
1253 EXTERNAL_LINKEDIN_OIDC_EMAIL_OPTIONAL: z.boolean().optional(),
1254 }),
1255 ]),
1256 misc: {
1257 iconKey: 'linkedin-icon',
1258 requiresRedirect: true,
1259 },
1260}
1261
1262const EXTERNAL_PROVIDER_NOTION = {
1263 $schema: JSON_SCHEMA_VERSION,
1264 type: 'object',
1265 title: 'Notion',
1266 link: `${DOCS_URL}/guides/auth/social-login/auth-notion`,
1267 properties: {
1268 EXTERNAL_NOTION_ENABLED: {
1269 title: 'Notion enabled',
1270 type: 'boolean',
1271 },
1272 EXTERNAL_NOTION_CLIENT_ID: {
1273 title: 'OAuth client ID',
1274 type: 'string',
1275 },
1276 EXTERNAL_NOTION_SECRET: {
1277 title: 'OAuth client secret',
1278 type: 'string',
1279 isSecret: true,
1280 },
1281 EXTERNAL_NOTION_EMAIL_OPTIONAL: {
1282 title: 'Allow users without an email',
1283 description:
1284 'Allows the user to successfully authenticate when the provider does not return an email address.',
1285 type: 'boolean',
1286 },
1287 },
1288 validationSchema: z.discriminatedUnion('EXTERNAL_NOTION_ENABLED', [
1289 z.object({
1290 EXTERNAL_NOTION_ENABLED: z.literal(false),
1291 EXTERNAL_NOTION_CLIENT_ID: z.string().optional(),
1292 EXTERNAL_NOTION_SECRET: z.string().optional(),
1293 EXTERNAL_NOTION_EMAIL_OPTIONAL: z.boolean().optional(),
1294 }),
1295 z.object({
1296 EXTERNAL_NOTION_ENABLED: z.literal(true),
1297 EXTERNAL_NOTION_CLIENT_ID: z.string().min(1, 'OAuth client ID is required'),
1298 EXTERNAL_NOTION_SECRET: z.string().min(1, 'OAuth client secret is required'),
1299 EXTERNAL_NOTION_EMAIL_OPTIONAL: z.boolean().optional(),
1300 }),
1301 ]),
1302 misc: {
1303 iconKey: 'notion-icon',
1304 requiresRedirect: true,
1305 },
1306}
1307
1308const EXTERNAL_PROVIDER_TWITCH = {
1309 $schema: JSON_SCHEMA_VERSION,
1310 type: 'object',
1311 title: 'Twitch',
1312 link: `${DOCS_URL}/guides/auth/social-login/auth-twitch`,
1313 properties: {
1314 EXTERNAL_TWITCH_ENABLED: {
1315 title: 'Twitch enabled',
1316 type: 'boolean',
1317 },
1318 EXTERNAL_TWITCH_CLIENT_ID: {
1319 title: 'Client ID',
1320 type: 'string',
1321 },
1322 EXTERNAL_TWITCH_SECRET: {
1323 title: 'Client secret',
1324 type: 'string',
1325 isSecret: true,
1326 },
1327 EXTERNAL_TWITCH_EMAIL_OPTIONAL: {
1328 title: 'Allow users without an email',
1329 description:
1330 'Allows the user to successfully authenticate when the provider does not return an email address.',
1331 type: 'boolean',
1332 },
1333 },
1334 validationSchema: z.discriminatedUnion('EXTERNAL_TWITCH_ENABLED', [
1335 z.object({
1336 EXTERNAL_TWITCH_ENABLED: z.literal(false),
1337 EXTERNAL_TWITCH_CLIENT_ID: z.string().optional(),
1338 EXTERNAL_TWITCH_SECRET: z.string().optional(),
1339 EXTERNAL_TWITCH_EMAIL_OPTIONAL: z.boolean().optional(),
1340 }),
1341 z.object({
1342 EXTERNAL_TWITCH_ENABLED: z.literal(true),
1343 EXTERNAL_TWITCH_CLIENT_ID: z.string().min(1, 'Client ID is required'),
1344 EXTERNAL_TWITCH_SECRET: z.string().min(1, 'Client secret is required'),
1345 EXTERNAL_TWITCH_EMAIL_OPTIONAL: z.boolean().optional(),
1346 }),
1347 ]),
1348 misc: {
1349 iconKey: 'twitch-icon',
1350 requiresRedirect: true,
1351 },
1352}
1353
1354const EXTERNAL_PROVIDER_TWITTER = {
1355 $schema: JSON_SCHEMA_VERSION,
1356 type: 'object',
1357 title: 'Twitter (Deprecated)',
1358 link: `${DOCS_URL}/guides/auth/social-login/auth-twitter`,
1359 properties: {
1360 EXTERNAL_TWITTER_ENABLED: {
1361 title: 'Twitter enabled',
1362 type: 'boolean',
1363 },
1364 EXTERNAL_TWITTER_CLIENT_ID: {
1365 title: 'API Key',
1366 type: 'string',
1367 },
1368 EXTERNAL_TWITTER_SECRET: {
1369 title: 'API Secret Key',
1370 type: 'string',
1371 isSecret: true,
1372 },
1373 EXTERNAL_TWITTER_EMAIL_OPTIONAL: {
1374 title: 'Allow users without an email',
1375 description:
1376 'Allows the user to successfully authenticate when the provider does not return an email address.',
1377 type: 'boolean',
1378 },
1379 },
1380 validationSchema: z.discriminatedUnion('EXTERNAL_TWITTER_ENABLED', [
1381 z.object({
1382 EXTERNAL_TWITTER_ENABLED: z.literal(false),
1383 EXTERNAL_TWITTER_CLIENT_ID: z.string().optional(),
1384 EXTERNAL_TWITTER_SECRET: z.string().optional(),
1385 EXTERNAL_TWITTER_EMAIL_OPTIONAL: z.boolean().optional(),
1386 }),
1387 z.object({
1388 EXTERNAL_TWITTER_ENABLED: z.literal(true),
1389 EXTERNAL_TWITTER_CLIENT_ID: z.string().min(1, 'API Key is required'),
1390 EXTERNAL_TWITTER_SECRET: z.string().min(1, 'API Secret Key is required'),
1391 EXTERNAL_TWITTER_EMAIL_OPTIONAL: z.boolean().optional(),
1392 }),
1393 ]),
1394 misc: {
1395 iconKey: 'twitter-icon',
1396 requiresRedirect: true,
1397 },
1398}
1399
1400const EXTERNAL_PROVIDER_X = {
1401 $schema: JSON_SCHEMA_VERSION,
1402 type: 'object',
1403 title: 'X / Twitter (OAuth 2.0)',
1404 link: `${DOCS_URL}/guides/auth/social-login/auth-twitter`,
1405 properties: {
1406 EXTERNAL_X_ENABLED: {
1407 title: 'X / Twitter enabled',
1408 type: 'boolean',
1409 },
1410 EXTERNAL_X_CLIENT_ID: {
1411 title: 'Client ID',
1412 type: 'string',
1413 },
1414 EXTERNAL_X_SECRET: {
1415 title: 'Client Secret',
1416 type: 'string',
1417 isSecret: true,
1418 },
1419 EXTERNAL_X_EMAIL_OPTIONAL: {
1420 title: 'Allow users without an email',
1421 description:
1422 'Allows the user to successfully authenticate when the provider does not return an email address.',
1423 type: 'boolean',
1424 },
1425 },
1426 validationSchema: z.discriminatedUnion('EXTERNAL_X_ENABLED', [
1427 z.object({
1428 EXTERNAL_X_ENABLED: z.literal(false),
1429 EXTERNAL_X_CLIENT_ID: z.string().optional(),
1430 EXTERNAL_X_SECRET: z.string().optional(),
1431 EXTERNAL_X_EMAIL_OPTIONAL: z.boolean().optional(),
1432 }),
1433 z.object({
1434 EXTERNAL_X_ENABLED: z.literal(true),
1435 EXTERNAL_X_CLIENT_ID: z.string().min(1, 'Client ID is required'),
1436 EXTERNAL_X_SECRET: z.string().min(1, 'Client Secret is required'),
1437 EXTERNAL_X_EMAIL_OPTIONAL: z.boolean().optional(),
1438 }),
1439 ]),
1440 misc: {
1441 iconKey: 'x-icon',
1442 hasLightIcon: true,
1443 requiresRedirect: true,
1444 },
1445}
1446
1447const EXTERNAL_PROVIDER_SLACK = {
1448 $schema: JSON_SCHEMA_VERSION,
1449 type: 'object',
1450 title: 'Slack (Deprecated)',
1451 link: `${DOCS_URL}/guides/auth/social-login/auth-slack`,
1452 properties: {
1453 EXTERNAL_SLACK_ENABLED: {
1454 title: 'Slack enabled',
1455 type: 'boolean',
1456 },
1457 EXTERNAL_SLACK_CLIENT_ID: {
1458 title: 'Client ID',
1459 type: 'string',
1460 },
1461 EXTERNAL_SLACK_SECRET: {
1462 title: 'Client Secret',
1463 type: 'string',
1464 isSecret: true,
1465 },
1466 EXTERNAL_SLACK_EMAIL_OPTIONAL: {
1467 title: 'Allow users without an email',
1468 description:
1469 'Allows the user to successfully authenticate when the provider does not return an email address.',
1470 type: 'boolean',
1471 },
1472 },
1473 validationSchema: z.discriminatedUnion('EXTERNAL_SLACK_ENABLED', [
1474 z.object({
1475 EXTERNAL_SLACK_ENABLED: z.literal(false),
1476 EXTERNAL_SLACK_CLIENT_ID: z.string().optional(),
1477 EXTERNAL_SLACK_SECRET: z.string().optional(),
1478 EXTERNAL_SLACK_EMAIL_OPTIONAL: z.boolean().optional(),
1479 }),
1480 z.object({
1481 EXTERNAL_SLACK_ENABLED: z.literal(true),
1482 EXTERNAL_SLACK_CLIENT_ID: z.string().min(1, 'Client ID is required'),
1483 EXTERNAL_SLACK_SECRET: z.string().min(1, 'Client Secret is required'),
1484 EXTERNAL_SLACK_EMAIL_OPTIONAL: z.boolean().optional(),
1485 }),
1486 ]),
1487 misc: {
1488 iconKey: 'slack-icon',
1489 requiresRedirect: true,
1490 },
1491}
1492
1493const EXTERNAL_PROVIDER_SLACK_OIDC = {
1494 $schema: JSON_SCHEMA_VERSION,
1495 type: 'object',
1496 title: 'Slack (OIDC)',
1497 key: 'slack_oidc',
1498 link: `${DOCS_URL}/guides/auth/social-login/auth-slack`,
1499 properties: {
1500 EXTERNAL_SLACK_OIDC_ENABLED: {
1501 title: 'Slack enabled',
1502 type: 'boolean',
1503 },
1504 EXTERNAL_SLACK_OIDC_CLIENT_ID: {
1505 title: 'Client ID',
1506 type: 'string',
1507 },
1508 EXTERNAL_SLACK_OIDC_SECRET: {
1509 title: 'Client Secret',
1510 type: 'string',
1511 isSecret: true,
1512 },
1513 EXTERNAL_SLACK_OIDC_EMAIL_OPTIONAL: {
1514 title: 'Allow users without an email',
1515 description:
1516 'Allows the user to successfully authenticate when the provider does not return an email address.',
1517 type: 'boolean',
1518 },
1519 },
1520 validationSchema: z.discriminatedUnion('EXTERNAL_SLACK_OIDC_ENABLED', [
1521 z.object({
1522 EXTERNAL_SLACK_OIDC_ENABLED: z.literal(false),
1523 EXTERNAL_SLACK_OIDC_CLIENT_ID: z.string().optional(),
1524 EXTERNAL_SLACK_OIDC_SECRET: z.string().optional(),
1525 EXTERNAL_SLACK_OIDC_EMAIL_OPTIONAL: z.boolean().optional(),
1526 }),
1527 z.object({
1528 EXTERNAL_SLACK_OIDC_ENABLED: z.literal(true),
1529 EXTERNAL_SLACK_OIDC_CLIENT_ID: z.string().min(1, 'Client ID is required'),
1530 EXTERNAL_SLACK_OIDC_SECRET: z.string().min(1, 'Client Secret is required'),
1531 EXTERNAL_SLACK_OIDC_EMAIL_OPTIONAL: z.boolean().optional(),
1532 }),
1533 ]),
1534 misc: {
1535 iconKey: 'slack-icon',
1536 requiresRedirect: true,
1537 },
1538}
1539
1540const EXTERNAL_PROVIDER_SPOTIFY = {
1541 $schema: JSON_SCHEMA_VERSION,
1542 type: 'object',
1543 title: 'Spotify',
1544 link: `${DOCS_URL}/guides/auth/social-login/auth-spotify`,
1545 properties: {
1546 EXTERNAL_SPOTIFY_ENABLED: {
1547 title: 'Spotify enabled',
1548 type: 'boolean',
1549 },
1550 EXTERNAL_SPOTIFY_CLIENT_ID: {
1551 title: 'Client ID',
1552 type: 'string',
1553 },
1554 EXTERNAL_SPOTIFY_SECRET: {
1555 title: 'Client Secret',
1556 type: 'string',
1557 isSecret: true,
1558 },
1559 EXTERNAL_SPOTIFY_EMAIL_OPTIONAL: {
1560 title: 'Allow users without an email',
1561 description:
1562 'Allows the user to successfully authenticate when the provider does not return an email address.',
1563 type: 'boolean',
1564 },
1565 },
1566 validationSchema: z.discriminatedUnion('EXTERNAL_SPOTIFY_ENABLED', [
1567 z.object({
1568 EXTERNAL_SPOTIFY_ENABLED: z.literal(false),
1569 EXTERNAL_SPOTIFY_CLIENT_ID: z.string().optional(),
1570 EXTERNAL_SPOTIFY_SECRET: z.string().optional(),
1571 EXTERNAL_SPOTIFY_EMAIL_OPTIONAL: z.boolean().optional(),
1572 }),
1573 z.object({
1574 EXTERNAL_SPOTIFY_ENABLED: z.literal(true),
1575 EXTERNAL_SPOTIFY_CLIENT_ID: z.string().min(1, 'Client ID is required'),
1576 EXTERNAL_SPOTIFY_SECRET: z.string().min(1, 'Client Secret is required'),
1577 EXTERNAL_SPOTIFY_EMAIL_OPTIONAL: z.boolean().optional(),
1578 }),
1579 ]),
1580 misc: {
1581 iconKey: 'spotify-icon',
1582 requiresRedirect: true,
1583 },
1584}
1585
1586const EXTERNAL_PROVIDER_WORKOS = {
1587 $schema: JSON_SCHEMA_VERSION,
1588 type: 'object',
1589 title: 'WorkOS',
1590 link: `${DOCS_URL}/guides/auth/social-login/auth-workos`,
1591 properties: {
1592 EXTERNAL_WORKOS_ENABLED: {
1593 title: 'WorkOS enabled',
1594 type: 'boolean',
1595 },
1596 EXTERNAL_WORKOS_URL: {
1597 title: 'WorkOS URL',
1598 type: 'string',
1599 },
1600 EXTERNAL_WORKOS_CLIENT_ID: {
1601 title: 'Client ID',
1602 type: 'string',
1603 },
1604 EXTERNAL_WORKOS_SECRET: {
1605 title: 'Secret Key',
1606 type: 'string',
1607 isSecret: true,
1608 },
1609 },
1610 validationSchema: z.discriminatedUnion('EXTERNAL_WORKOS_ENABLED', [
1611 z.object({
1612 EXTERNAL_WORKOS_ENABLED: z.literal(false),
1613 EXTERNAL_WORKOS_URL: z.string().optional(),
1614 EXTERNAL_WORKOS_CLIENT_ID: z.string().optional(),
1615 EXTERNAL_WORKOS_SECRET: z.string().optional(),
1616 }),
1617 z.object({
1618 EXTERNAL_WORKOS_ENABLED: z.literal(true),
1619 EXTERNAL_WORKOS_URL: z
1620 .string()
1621 .min(1, 'WorkOS URL is required')
1622 .regex(urlRegex(), 'Must be a valid URL'),
1623 EXTERNAL_WORKOS_CLIENT_ID: z.string().min(1, 'Client ID is required'),
1624 EXTERNAL_WORKOS_SECRET: z.string().min(1, 'Client Secret is required'),
1625 }),
1626 ]),
1627 misc: {
1628 iconKey: 'workos-icon',
1629 requiresRedirect: true,
1630 },
1631}
1632
1633const EXTERNAL_PROVIDER_ZOOM = {
1634 $schema: JSON_SCHEMA_VERSION,
1635 type: 'object',
1636 title: 'Zoom',
1637 link: `${DOCS_URL}/guides/auth/social-login/auth-zoom`,
1638 properties: {
1639 EXTERNAL_ZOOM_ENABLED: {
1640 title: 'Zoom enabled',
1641 type: 'boolean',
1642 },
1643 EXTERNAL_ZOOM_CLIENT_ID: {
1644 title: 'Client ID',
1645 type: 'string',
1646 },
1647 EXTERNAL_ZOOM_SECRET: {
1648 title: 'Client secret',
1649 type: 'string',
1650 isSecret: true,
1651 },
1652 EXTERNAL_ZOOM_EMAIL_OPTIONAL: {
1653 title: 'Allow users without an email',
1654 description:
1655 'Allows the user to successfully authenticate when the provider does not return an email address.',
1656 type: 'boolean',
1657 },
1658 },
1659 validationSchema: z.discriminatedUnion('EXTERNAL_ZOOM_ENABLED', [
1660 z.object({
1661 EXTERNAL_ZOOM_ENABLED: z.literal(false),
1662 EXTERNAL_ZOOM_CLIENT_ID: z.string().optional(),
1663 EXTERNAL_ZOOM_SECRET: z.string().optional(),
1664 EXTERNAL_ZOOM_EMAIL_OPTIONAL: z.boolean().optional(),
1665 }),
1666 z.object({
1667 EXTERNAL_ZOOM_ENABLED: z.literal(true),
1668 EXTERNAL_ZOOM_CLIENT_ID: z.string().min(1, 'Client ID is required'),
1669 EXTERNAL_ZOOM_SECRET: z.string().min(1, 'Client secret is required'),
1670 EXTERNAL_ZOOM_EMAIL_OPTIONAL: z.boolean().optional(),
1671 }),
1672 ]),
1673 misc: {
1674 iconKey: 'zoom-icon',
1675 requiresRedirect: true,
1676 },
1677}
1678
1679const PROVIDER_SAML = {
1680 $schema: JSON_SCHEMA_VERSION,
1681 type: 'object',
1682 title: 'SAML 2.0',
1683 link: `${DOCS_URL}/guides/auth/enterprise-sso/auth-sso-saml`,
1684 properties: {
1685 SAML_ENABLED: {
1686 title: 'Enable SAML 2.0 Single Sign-on',
1687 description: `You will need to use the [Briven CLI](${DOCS_URL}/guides/auth/sso/auth-sso-saml#managing-saml-20-connections) to set up SAML after enabling it`,
1688 type: 'boolean',
1689 },
1690 SAML_EXTERNAL_URL: {
1691 title: 'SAML metadata URL',
1692 description:
1693 'You may use a different SAML metadata URL from what is defined with the API External URL. Please validate that your SAML External URL can reach the Custom Domain or Project URL.',
1694 descriptionOptional: 'Optional',
1695 type: 'string',
1696 },
1697 SAML_ALLOW_ENCRYPTED_ASSERTIONS: {
1698 title: 'Allow encrypted SAML Assertions',
1699 description:
1700 'Some SAML Identity Providers require support for encrypted assertions. Usually this is not necessary.',
1701 descriptionOptional: 'Optional',
1702 type: 'boolean',
1703 },
1704 },
1705 validationSchema: z.discriminatedUnion('SAML_ENABLED', [
1706 z.object({
1707 SAML_ENABLED: z.literal(false),
1708 SAML_EXTERNAL_URL: z.string().optional(),
1709 SAML_ALLOW_ENCRYPTED_ASSERTIONS: z.boolean().optional(),
1710 }),
1711 z.object({
1712 SAML_ENABLED: z.literal(true),
1713 SAML_EXTERNAL_URL: z.string().refine((value) => {
1714 // Allow empty values
1715 if (value == '') return true
1716 return urlRegex().test(value)
1717 }, 'Must be a valid URL'),
1718 SAML_ALLOW_ENCRYPTED_ASSERTIONS: z.boolean().optional(),
1719 }),
1720 ]),
1721 misc: {
1722 iconKey: 'saml-icon',
1723 },
1724}
1725
1726const PROVIDER_WEB3 = {
1727 $schema: JSON_SCHEMA_VERSION,
1728 type: 'object',
1729 title: 'Web3 Wallet',
1730 link: `${DOCS_URL}/guides/auth/auth-web3`,
1731 properties: {
1732 EXTERNAL_WEB3_ETHEREUM_ENABLED: {
1733 title: 'Enable Sign in with Ethereum',
1734 description:
1735 'Allow Ethereum wallets to sign in to your project via the Sign in with Ethereum (EIP-4361). Set up [attack protection](../auth/protection) and adjust [rate limits](../auth/rate-limits) to counter abuse.',
1736 type: 'boolean',
1737 },
1738 EXTERNAL_WEB3_SOLANA_ENABLED: {
1739 title: 'Enable Sign in with Solana',
1740 description:
1741 'Allow Solana wallet holders to sign in to your project via the Sign in with Solana (SIWS, EIP-4361) standard. Set up [attack protection](../auth/protection) and adjust [rate limits](../auth/rate-limits) to counter abuse.',
1742 type: 'boolean',
1743 },
1744 },
1745 validationSchema: z.object({
1746 EXTERNAL_WEB3_ETHEREUM_ENABLED: z.boolean(),
1747 EXTERNAL_WEB3_SOLANA_ENABLED: z.boolean(),
1748 }),
1749 misc: {
1750 iconKey: 'web3-icon',
1751 },
1752}
1753
1754export const PROVIDERS_SCHEMAS = [
1755 PROVIDER_EMAIL,
1756 PROVIDER_PHONE,
1757 PROVIDER_SAML,
1758 PROVIDER_WEB3,
1759 EXTERNAL_PROVIDER_APPLE,
1760 EXTERNAL_PROVIDER_AZURE,
1761 EXTERNAL_PROVIDER_BITBUCKET,
1762 EXTERNAL_PROVIDER_DISCORD,
1763 EXTERNAL_PROVIDER_FACEBOOK,
1764 EXTERNAL_PROVIDER_FIGMA,
1765 EXTERNAL_PROVIDER_GITHUB,
1766 EXTERNAL_PROVIDER_GITLAB,
1767 EXTERNAL_PROVIDER_GOOGLE,
1768 EXTERNAL_PROVIDER_KAKAO,
1769 EXTERNAL_PROVIDER_KEYCLOAK,
1770 EXTERNAL_PROVIDER_LINKEDIN_OIDC,
1771 EXTERNAL_PROVIDER_NOTION,
1772 EXTERNAL_PROVIDER_TWITCH,
1773 EXTERNAL_PROVIDER_X,
1774 EXTERNAL_PROVIDER_TWITTER,
1775 EXTERNAL_PROVIDER_SLACK_OIDC,
1776 EXTERNAL_PROVIDER_SLACK,
1777 EXTERNAL_PROVIDER_SPOTIFY,
1778 EXTERNAL_PROVIDER_WORKOS,
1779 EXTERNAL_PROVIDER_ZOOM,
1780]