1
0
forked from baron/baron-sso

feat(auth): lock affiliation type on frontend based on verified email domain (#500)

This commit is contained in:
2026-04-07 13:01:30 +09:00
parent 4e7f3e7235
commit b3a7f47cf7
3 changed files with 99 additions and 64 deletions

View File

@@ -923,13 +923,19 @@ class AuthProxyService {
}
}
static Future<List<Map<String, dynamic>>> getActiveTenants() async {
final url = Uri.parse('$_baseUrl/api/v1/auth/signup/tenants');
final response = await http.get(url);
static Future<List<Map<String, dynamic>>> getActiveTenants({
String? email,
}) async {
var uriString = '$_baseUrl/api/v1/auth/signup/tenants';
if (email != null && email.isNotEmpty) {
uriString += '?email=${Uri.encodeComponent(email)}';
}
final url = Uri.parse(uriString);
final response = await http.get(url);
if (response.statusCode == 200) {
final List<dynamic> data = jsonDecode(response.body);
return data.cast<Map<String, dynamic>>();
final List<dynamic> list = jsonDecode(response.body);
return list.cast<Map<String, dynamic>>();
}
return [];
}
@@ -953,7 +959,7 @@ class AuthProxyService {
}
}
static Future<bool> verifySignupCode(
static Future<Map<String, dynamic>> verifySignupCode(
String target,
String type,
String code,
@@ -967,10 +973,9 @@ class AuthProxyService {
);
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
return data['success'] ?? false;
return jsonDecode(response.body);
}
return false;
throw Exception('Verification failed');
}
static Future<void> signup({

View File

@@ -44,6 +44,7 @@ class _SignupScreenState extends State<SignupScreen> {
bool _isEmailVerified = false;
bool _isPhoneVerified = false;
String _affiliationType = 'GENERAL';
bool _isAffiliateLocked = false;
String? _companyCode;
bool _termsAccepted = false;
bool _privacyAccepted = false;
@@ -72,15 +73,23 @@ class _SignupScreenState extends State<SignupScreen> {
void initState() {
super.initState();
_loadPolicy();
_fetchTenants();
// initState에서는 _fetchTenants() 호출 제외
}
Future<void> _fetchTenants() async {
if (!_isEmailVerified) return;
try {
final tenants = await AuthProxyService.getActiveTenants();
final tenants = await AuthProxyService.getActiveTenants(
email: _emailController.text.trim(),
);
if (mounted) {
setState(() {
_tenants = tenants;
if (_tenants.isNotEmpty && _affiliationType == 'AFFILIATE') {
// 목록이 있는데 아직 아무것도 선택되지 않았다면 자동 할당 가능
_companyCode ??= _tenants.first['slug'];
}
});
}
} catch (e) {
@@ -195,18 +204,32 @@ class _SignupScreenState extends State<SignupScreen> {
final code = _emailCodeController.text.trim();
if (code.length != 6) return;
try {
final success = await AuthProxyService.verifySignupCode(
final res = await AuthProxyService.verifySignupCode(
_emailController.text.trim(),
'email',
code,
);
if (success) {
if (res['success'] == true) {
setState(() {
_isEmailVerified = true;
_emailTimer?.cancel();
_emailSeconds = 0;
_emailError = null;
if (res['isAffiliate'] == true) {
_affiliationType = 'AFFILIATE';
_isAffiliateLocked = true;
} else {
_affiliationType = 'GENERAL';
_companyCode = null;
_isAffiliateLocked = true;
}
});
// Only fetch tenants if it's an affiliate domain
if (res['isAffiliate'] == true) {
_fetchTenants();
}
} else {
setState(
() => _emailError = tr('msg.userfront.signup.email.code_mismatch'),
@@ -248,12 +271,12 @@ class _SignupScreenState extends State<SignupScreen> {
final code = _phoneCodeController.text.trim();
if (code.length != 6) return;
try {
final success = await AuthProxyService.verifySignupCode(
final res = await AuthProxyService.verifySignupCode(
_phoneController.text.trim(),
'phone',
code,
);
if (success) {
if (res['success'] == true) {
setState(() {
_isPhoneVerified = true;
_phoneTimer?.cancel();
@@ -1445,17 +1468,19 @@ class _SignupScreenState extends State<SignupScreen> {
),
),
],
onChanged: (val) {
if (val == null) {
return;
}
setState(() {
_affiliationType = val;
if (_affiliationType == 'GENERAL') {
_companyCode = null;
}
});
},
onChanged: _isAffiliateLocked
? null
: (val) {
if (val == null) {
return;
}
setState(() {
_affiliationType = val;
if (_affiliationType == 'GENERAL') {
_companyCode = null;
}
});
},
),
AnimatedSize(
duration: const Duration(milliseconds: 180),