@@ -1759,18 +1759,34 @@ export class HttpDispatcher {
17591759 const req = body || { } ;
17601760 // Resolve `__session__` placeholders from the active session so clients
17611761 // can omit these fields and let the server infer them.
1762+ // Use `resolveCallerUserId` which properly converts plain header
1763+ // objects to `Headers` instances and probes both
1764+ // `authService.auth.api.getSession` and `authService.api.getSession`
1765+ // (better-auth's API shape differs across wrappings).
17621766 if ( req . organization_id === '__session__' || req . created_by === '__session__' ) {
17631767 try {
1764- const authService : any = await this . getService ( CoreServiceName . enum . auth ) ;
1765- const sessionData = await authService ?. api ?. getSession ?. ( {
1766- headers : _context ?. request ?. headers ,
1767- } ) ;
1768+ const userId = await this . resolveCallerUserId ( _context ) ;
1769+ if ( req . created_by === '__session__' ) {
1770+ req . created_by = userId ?? 'system' ;
1771+ }
17681772 if ( req . organization_id === '__session__' ) {
1773+ // We still need the activeOrganizationId — fetch the
1774+ // session directly via the helper-built Headers.
1775+ const authService : any = await this . resolveService ( CoreServiceName . enum . auth ) ;
1776+ const rawHeaders = _context ?. request ?. headers ;
1777+ let headers : any = rawHeaders ;
1778+ if ( rawHeaders && typeof rawHeaders === 'object' && typeof ( rawHeaders as any ) . get !== 'function' ) {
1779+ const h = new Headers ( ) ;
1780+ for ( const [ k , v ] of Object . entries ( rawHeaders as Record < string , any > ) ) {
1781+ if ( v == null ) continue ;
1782+ h . set ( k , Array . isArray ( v ) ? v . join ( ', ' ) : String ( v ) ) ;
1783+ }
1784+ headers = h ;
1785+ }
1786+ const apiObj = authService ?. auth ?. api ?? authService ?. api ;
1787+ const sessionData = await apiObj ?. getSession ?. call ( apiObj , { headers } ) ;
17691788 req . organization_id = sessionData ?. session ?. activeOrganizationId ?? undefined ;
17701789 }
1771- if ( req . created_by === '__session__' ) {
1772- req . created_by = sessionData ?. user ?. id ?? 'system' ;
1773- }
17741790 } catch {
17751791 // Fall through — validation below will reject missing fields.
17761792 }
@@ -1869,67 +1885,33 @@ export class HttpDispatcher {
18691885 // pre-seed a `sys_user` row for this person on first boot.
18701886 // Without this, the owner lands on their own project as a
18711887 // brand-new SSO JIT user (no admin role, no membership) and
1872- // has to manually promote themselves. We stash the cloud
1873- // identity here — the project DB does not exist yet — and let
1874- // ArtifactKernelFactory replay the seed once the project's
1875- // sys_user table is provisioned. Best-effort: failure to
1876- // resolve the user must not abort project creation.
1877- //
1878- // We prefer `req.created_by` (already resolved upstream from
1879- // `__session__` or sent explicitly by the cloud frontend) and
1880- // look the user row up directly via objectql. The previous
1881- // approach — calling `authService.api.getSession({headers})`
1882- // a SECOND time — was unreliable because the cloud's project-
1883- // create route is often invoked via an internal RPC where the
1884- // original browser cookies do not propagate.
1888+ // has to manually promote themselves. Best-effort: failure
1889+ // to resolve must not abort project creation.
18851890 try {
1886- let ownerUserId : string | undefined = req . created_by && req . created_by !== 'system'
1887- ? String ( req . created_by )
1888- : undefined ;
1889- let ownerEmail : string | undefined ;
1890- let ownerName : string | undefined ;
1891- let ownerImage : string | undefined ;
1892-
1891+ // Prefer the value already resolved by the upstream
1892+ // `__session__` block (now backed by `resolveCallerUserId`);
1893+ // fall back to `resolveCallerUserId` directly for callers
1894+ // that omitted `created_by` entirely.
1895+ let ownerUserId : string | undefined =
1896+ req . created_by && req . created_by !== 'system'
1897+ ? String ( req . created_by )
1898+ : undefined ;
1899+ if ( ! ownerUserId ) {
1900+ ownerUserId = await this . resolveCallerUserId ( _context ) ;
1901+ }
18931902 if ( ownerUserId ) {
18941903 const userRow = await ql . find ( 'sys_user' , { where : { id : ownerUserId } } as any ) ;
18951904 const userRows = Array . isArray ( userRow ) ? userRow : ( userRow ?. value ?? [ ] ) ;
18961905 const u = Array . isArray ( userRows ) && userRows . length > 0 ? userRows [ 0 ] : null ;
18971906 if ( u ?. email ) {
1898- ownerEmail = String ( u . email ) ;
1899- ownerName = u . name ? String ( u . name ) : undefined ;
1900- ownerImage = u . image ? String ( u . image ) : undefined ;
1907+ ( baseMetadata as any ) . ownerSeed = {
1908+ userId : String ( ownerUserId ) ,
1909+ email : String ( u . email ) ,
1910+ name : u . name ? String ( u . name ) : null ,
1911+ image : u . image ? String ( u . image ) : null ,
1912+ } ;
19011913 }
19021914 }
1903-
1904- // Fall back to the session probe if `created_by` was not
1905- // set (e.g. CLI scripts) — preserves the prior behaviour
1906- // when cookies are available.
1907- if ( ! ownerEmail ) {
1908- try {
1909- const authService : any = await this . getService ( CoreServiceName . enum . auth ) ;
1910- const sessionData = await authService ?. api ?. getSession ?.( {
1911- headers : _context ?. request ?. headers ,
1912- } ) ;
1913- const u = sessionData ?. user ;
1914- if ( u ?. id && u ?. email ) {
1915- ownerUserId = String ( u . id ) ;
1916- ownerEmail = String ( u . email ) ;
1917- ownerName = u . name ? String ( u . name ) : undefined ;
1918- ownerImage = u . image ? String ( u . image ) : undefined ;
1919- }
1920- } catch {
1921- // session lookup is best-effort
1922- }
1923- }
1924-
1925- if ( ownerUserId && ownerEmail ) {
1926- ( baseMetadata as any ) . ownerSeed = {
1927- userId : ownerUserId ,
1928- email : ownerEmail ,
1929- name : ownerName ?? null ,
1930- image : ownerImage ?? null ,
1931- } ;
1932- }
19331915 } catch {
19341916 // owner lookup failed entirely — skip seed; later access
19351917 // flows still work via the platform-SSO JIT path.
0 commit comments