@@ -82,10 +82,45 @@ const PUBLIC_EMAIL_DOMAINS = new Set([
8282 "fastmail.com" ,
8383] ) ;
8484
85+ const CORPORATE_DOMAIN_ALIASES : Record < string , string [ ] > = {
86+ "aws.com" : [ "amazon.com" , "aws.amazon.com" ] ,
87+ "amazon.com" : [ "aws.com" , "aws.amazon.com" ] ,
88+ } ;
89+
90+ /**
91+ * Extracts all allowed email addresses (including `coordinatorEmails` and comma-separated lists)
92+ * from one or more Sanity booking / sponsorLead documents.
93+ */
94+ export function extractPortalAllowedEmails (
95+ ...docs : Array < Record < string , any > | null | undefined >
96+ ) : string [ ] {
97+ const out = new Set < string > ( ) ;
98+ for ( const doc of docs ) {
99+ if ( ! doc || typeof doc !== "object" ) continue ;
100+ const rawStrings = [
101+ doc . guestEmail ,
102+ doc . contactEmail ,
103+ doc . coordinatorEmail ,
104+ ...( Array . isArray ( doc . coordinatorEmails ) ? doc . coordinatorEmails : [ ] ) ,
105+ ...( Array . isArray ( doc . allowedEmails ) ? doc . allowedEmails : [ ] ) ,
106+ ] ;
107+ for ( const item of rawStrings ) {
108+ if ( typeof item !== "string" ) continue ;
109+ for ( const part of item . split ( / [ , ; \s ] + / ) ) {
110+ const clean = part . trim ( ) . toLowerCase ( ) ;
111+ if ( clean && clean . includes ( "@" ) ) {
112+ out . add ( clean ) ;
113+ }
114+ }
115+ }
116+ }
117+ return Array . from ( out ) ;
118+ }
119+
85120/**
86121 * Checks whether an authenticated Better-Auth user's email is authorized to unlock
87- * a specific Studio Portal (exact guest/sponsor email, same non-public corporate domain,
88- * or CodingCat.dev host admin).
122+ * a specific Studio Portal (exact guest/sponsor email, coordinator email, same non-public
123+ * corporate domain, or CodingCat.dev host admin).
89124 */
90125export function isAllowedPortalEmail (
91126 candidateEmail : string ,
@@ -99,18 +134,19 @@ export function isAllowedPortalEmail(
99134 const candidateDomain = cleanCandidate . split ( "@" ) [ 1 ] || "" ;
100135
101136 for ( const rawAllowed of allowedEmails ) {
102- const cleanAllowed = ( rawAllowed || "" ) . trim ( ) . toLowerCase ( ) ;
103- if ( ! cleanAllowed || ! cleanAllowed . includes ( "@" ) ) continue ;
104- if ( cleanCandidate === cleanAllowed ) return true ;
105-
106- // Allow teammates from the same corporate email domain (e.g. @mastra.ai, @fusionauth.io)
107- const allowedDomain = cleanAllowed . split ( "@" ) [ 1 ] || "" ;
108- if (
109- allowedDomain &&
110- ! PUBLIC_EMAIL_DOMAINS . has ( allowedDomain ) &&
111- candidateDomain === allowedDomain
112- ) {
113- return true ;
137+ if ( ! rawAllowed ) continue ;
138+ for ( const part of String ( rawAllowed ) . split ( / [ , ; \s ] + / ) ) {
139+ const cleanAllowed = part . trim ( ) . toLowerCase ( ) ;
140+ if ( ! cleanAllowed || ! cleanAllowed . includes ( "@" ) ) continue ;
141+ if ( cleanCandidate === cleanAllowed ) return true ;
142+
143+ // Allow teammates from the same corporate email domain (e.g. @mastra.ai, @fusionauth.io, @aws.com <-> @amazon.com)
144+ const allowedDomain = cleanAllowed . split ( "@" ) [ 1 ] || "" ;
145+ if ( allowedDomain && ! PUBLIC_EMAIL_DOMAINS . has ( allowedDomain ) ) {
146+ if ( candidateDomain === allowedDomain ) return true ;
147+ const aliases = CORPORATE_DOMAIN_ALIASES [ allowedDomain ] || [ ] ;
148+ if ( aliases . includes ( candidateDomain ) ) return true ;
149+ }
114150 }
115151 }
116152
0 commit comments