err:=q.QueryRow(ctx,`SELECT ws.token_hash,ws.setup_id::text,COALESCE(ws.user_id::text,''),COALESCE(ws.issuer,''),COALESCE(ws.subject,''),ws.expires_at,ss.device_id::text FROM onboarding_web_session ws JOIN device_setup_session ss ON ss.id=ws.setup_id WHERE ws.token_hash=$1 AND ws.revoked_at IS NULL AND ws.expires_at>now()`,hash).Scan(&w.Hash,&w.SetupID,&w.UserID,&w.Issuer,&w.Subject,&w.Expires,&id)
err=tx.QueryRow(ctx,`SELECT EXISTS(SELECT 1 FROM user_account WHERE id=$1::uuid AND tenant_id=$2::uuid AND status='active')`,w.UserID,d.TenantID).Scan(&active)
err:=s.pool.QueryRow(ctx,`SELECT csrf_hash FROM onboarding_web_session WHERE token_hash=$1 AND revoked_at IS NULL AND expires_at>now()`,hash).Scan(&expected)
err:=s.pool.QueryRow(ctx,`SELECT ss.device_id::text FROM device_handoff h JOIN device_setup_session ss ON ss.id=h.setup_id WHERE h.token_hash=$1`,digest([]byte(in.Token))).Scan(&deviceID)
iferrors.Is(err,pgx.ErrNoRows){
returnout,errAccess
}
iferr!=nil{
returnout,err
}
tx,err:=s.pool.Begin(ctx)
iferr!=nil{
returnout,err
}
defertx.Rollback(ctx)
d,err:=readDevice(ctx,tx,deviceID,true)
iferr!=nil{
returnout,err
}
if!deviceAllowed(d){
returnout,errAccess
}
varsetupID,challengestring
varexpirestime.Time
varconsumed*time.Time
err=tx.QueryRow(ctx,`SELECT setup_id::text,code_challenge,expires_at,consumed_at FROM device_handoff WHERE token_hash=$1 FOR UPDATE`,digest([]byte(in.Token))).Scan(&setupID,&challenge,&expires,&consumed)
_,err=tx.Exec(ctx,`INSERT INTO onboarding_web_session(token_hash,setup_id,csrf_hash,expires_at) VALUES($1,$2::uuid,$3,$4)`,digest([]byte(out.Token)),setupID,digest([]byte(out.CSRF)),setup.Expires)
iferr!=nil{
returnout,err
}
_,err=tx.Exec(ctx,`UPDATE device_handoff SET consumed_at=now() WHERE token_hash=$1`,digest([]byte(in.Token)))
err=tx.QueryRow(ctx,`SELECT id::text FROM user_account WHERE tenant_id=$1::uuid AND oidc_issuer=$2 AND oidc_subject=$3 AND status='active' FOR SHARE`,d.TenantID,p.Issuer,p.Subject).Scan(&user)
iferrors.Is(err,pgx.ErrNoRows){
vartenantPublicstring
iferr=tx.QueryRow(ctx,`SELECT public_id::text FROM tenant WHERE id=$1::uuid`,d.TenantID).Scan(&tenantPublic);err!=nil{
err=tx.QueryRow(ctx,`INSERT INTO user_account(tenant_id,oidc_issuer,oidc_subject,display_name,platform_role,account_kind) VALUES($1::uuid,$2,$3,$4,'customer','customer') RETURNING id::text`,d.TenantID,p.Issuer,p.Subject,p.DisplayName).Scan(&user)
}
iferr!=nil{
returnout,err
}
out.Token=randomToken(32)
out.CSRF=randomToken(32)
out.AccountToken=randomToken(32)
_,err=tx.Exec(ctx,`INSERT INTO onboarding_web_session(token_hash,setup_id,csrf_hash,user_id,issuer,subject,expires_at) VALUES($1,$2::uuid,$3,$4::uuid,$5,$6,$7)`,digest([]byte(out.Token)),w.SetupID,digest([]byte(out.CSRF)),user,p.Issuer,p.Subject,w.Expires)
iferr!=nil{
returnout,err
}
_,err=tx.Exec(ctx,`UPDATE onboarding_web_session SET revoked_at=now() WHERE token_hash=$1`,w.Hash)
iferr!=nil{
returnout,err
}
_,err=tx.Exec(ctx,`INSERT INTO onboarding_account_session(token_hash,csrf_hash,user_id,expires_at) VALUES($1,$2,$3::uuid,$4)`,digest([]byte(out.AccountToken)),digest([]byte(out.CSRF)),user,s.now().Add(24*time.Hour))
err=tx.QueryRow(ctx,`SELECT id::text,household_id::text FROM subject_profile WHERE public_id=$1::uuid AND tenant_id=$2::uuid AND status='active'`,in.ElderID,d.TenantID).Scan(&subject,&household)
err=tx.QueryRow(ctx,`SELECT b.public_id::text,EXISTS(SELECT 1 FROM device_state_ack a WHERE a.device_id=b.device_id AND a.binding_generation=b.generation AND a.boot_id=$3::uuid) FROM device_binding b JOIN household_member hm ON hm.household_id=b.household_id AND hm.user_id=$2::uuid AND hm.status='active' JOIN user_account ua ON ua.id=hm.user_id AND ua.status='active' WHERE b.device_id=$1::uuid AND b.unbound_at IS NULL`,d.ID,w.UserID,d.BootID).Scan(&binding,&deviceSaved)
iferr!=nil&&!errors.Is(err,pgx.ErrNoRows){
returnnil,err
}
err=tx.QueryRow(ctx,`SELECT id::text FROM device_binding_claim WHERE setup_id=$1::uuid AND user_id=$2::uuid AND status='PENDING' AND expires_at>now()`,w.SetupID,w.UserID).Scan(&mine)
iferr!=nil&&!errors.Is(err,pgx.ErrNoRows){
returnnil,err
}
}
// Never expose a different household's progress through an old handoff.
err:=tx.QueryRow(ctx,`SELECT b.id::text FROM device_binding b JOIN household_member hm ON hm.household_id=b.household_id AND hm.user_id=$3::uuid AND hm.status='active' JOIN user_account ua ON ua.id=hm.user_id AND ua.status='active' JOIN device_state_snapshot ss ON ss.device_id=b.device_id AND ss.revision=$5 AND ss.binding_generation=b.generation AND ss.state->>'binding_state'='BOUND' WHERE b.device_id=$1::uuid AND b.public_id=$2::uuid AND b.unbound_at IS NULL AND b.generation=$4`,d.ID,in.BindingID,w.UserID,in.Generation,in.Revision).Scan(&bindingID)
iferrors.Is(err,pgx.ErrNoRows){
returnnil,errState
}
iferr!=nil{
returnnil,err
}
ifd.Generation!=in.Generation{
returnnil,errState
}
_,err=tx.Exec(ctx,`UPDATE device_setup_session SET web_ack_binding_id=$2::uuid,web_ack_generation=$3,web_ack_revision=$4,web_ack_at=now() WHERE id=$1::uuid`,w.SetupID,bindingID,in.Generation,in.Revision)
rows,err:=s.pool.Query(ctx,`SELECT h.public_id::text,h.name,sp.public_id::text,sp.preferred_name FROM household_member hm JOIN user_account ua ON ua.id=hm.user_id AND ua.status='active' JOIN household h ON h.id=hm.household_id AND h.tenant_id=hm.tenant_id AND h.status='active' LEFT JOIN subject_profile sp ON sp.household_id=h.id AND sp.tenant_id=h.tenant_id AND sp.status='active' WHERE hm.user_id=$1::uuid AND hm.tenant_id=$2::uuid AND hm.status='active' AND hm.role IN ('owner','admin','editor') ORDER BY h.name,sp.preferred_name`,w.UserID,w.Device.TenantID)
err=tx.QueryRow(ctx,`INSERT INTO household(tenant_id,name,owner_user_id) VALUES($1::uuid,$2,$3::uuid) RETURNING id::text,public_id::text`,d.TenantID,in.HouseholdName,w.UserID).Scan(&household,&public)
iferr!=nil{
returnnil,err
}
_,err=tx.Exec(ctx,`INSERT INTO household_member(tenant_id,household_id,user_id,role) VALUES($1::uuid,$2::uuid,$3::uuid,'owner')`,d.TenantID,household,w.UserID)
}else{
err=tx.QueryRow(ctx,`SELECT h.id::text,h.public_id::text FROM household h JOIN household_member hm ON hm.household_id=h.id AND hm.tenant_id=h.tenant_id WHERE h.public_id=$1::uuid AND h.tenant_id=$2::uuid AND h.status='active' AND hm.user_id=$3::uuid AND hm.status='active' AND hm.role IN ('owner','admin','editor') FOR SHARE OF h,hm`,in.HouseholdID,d.TenantID,w.UserID).Scan(&household,&public)
}
iferrors.Is(err,pgx.ErrNoRows){
returnnil,errAccess
}
iferr!=nil{
returnnil,err
}
varidstring
err=tx.QueryRow(ctx,`INSERT INTO subject_profile(tenant_id,household_id,preferred_name) VALUES($1::uuid,$2::uuid,$3) RETURNING public_id::text`,d.TenantID,household,in.Name).Scan(&id)