All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m13s
- IP138 在线 API + ip2region 离线库双通道归属地解析,60 秒熔断降级 - 5 条异常检测规则:地区不对/不可能旅行/频繁登录/团队遍地开花/海外IP太杂 - 飞书 interactive 卡片告警(红色严重/橙色警告),含辅助指标 - R2 自动封禁用户、R4 自动封禁团队,封禁即踢下线 - 系统设置页全局配置 + 团队详情页独立阈值覆盖 - 安全日志页面 + 管理员修改密码入口 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
100 lines
5.8 KiB
Python
100 lines
5.8 KiB
Python
# Generated by Django 4.2.29 on 2026-03-18 12:11
|
|
|
|
from django.conf import settings
|
|
from django.db import migrations, models
|
|
import django.db.models.deletion
|
|
|
|
|
|
class Migration(migrations.Migration):
|
|
|
|
dependencies = [
|
|
('accounts', '0007_set_existing_users_must_change_password_false'),
|
|
]
|
|
|
|
operations = [
|
|
migrations.AddField(
|
|
model_name='loginrecord',
|
|
name='geo_city',
|
|
field=models.CharField(blank=True, default='', max_length=50, verbose_name='城市'),
|
|
),
|
|
migrations.AddField(
|
|
model_name='loginrecord',
|
|
name='geo_country',
|
|
field=models.CharField(blank=True, default='', max_length=50, verbose_name='国家'),
|
|
),
|
|
migrations.AddField(
|
|
model_name='loginrecord',
|
|
name='geo_province',
|
|
field=models.CharField(blank=True, default='', max_length=50, verbose_name='省份'),
|
|
),
|
|
migrations.AddField(
|
|
model_name='loginrecord',
|
|
name='geo_source',
|
|
field=models.CharField(blank=True, default='', max_length=10, verbose_name='归属地来源'),
|
|
),
|
|
migrations.AddField(
|
|
model_name='loginrecord',
|
|
name='team',
|
|
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='login_records', to='accounts.team', verbose_name='所属团队'),
|
|
),
|
|
migrations.AddField(
|
|
model_name='team',
|
|
name='disabled_by',
|
|
field=models.CharField(blank=True, default='', max_length=10, verbose_name='禁用来源'),
|
|
),
|
|
migrations.AddField(
|
|
model_name='team',
|
|
name='expected_regions',
|
|
field=models.CharField(blank=True, default='', max_length=500, verbose_name='预期登录城市(逗号分隔)'),
|
|
),
|
|
migrations.AddField(
|
|
model_name='user',
|
|
name='disabled_by',
|
|
field=models.CharField(blank=True, default='', max_length=10, verbose_name='禁用来源'),
|
|
),
|
|
migrations.CreateModel(
|
|
name='TeamAnomalyConfig',
|
|
fields=[
|
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
('r1_enabled', models.BooleanField(blank=True, null=True, verbose_name='R1 开关')),
|
|
('r2_enabled', models.BooleanField(blank=True, null=True, verbose_name='R2 开关')),
|
|
('r2_window_seconds', models.IntegerField(blank=True, null=True, verbose_name='R2 时间窗口(秒)')),
|
|
('r3_enabled', models.BooleanField(blank=True, null=True, verbose_name='R3 开关')),
|
|
('r3_window_seconds', models.IntegerField(blank=True, null=True, verbose_name='R3 时间窗口(秒)')),
|
|
('r3_max_count', models.IntegerField(blank=True, null=True, verbose_name='R3 最大登录次数')),
|
|
('r4_enabled', models.BooleanField(blank=True, null=True, verbose_name='R4 开关')),
|
|
('r4_window_seconds', models.IntegerField(blank=True, null=True, verbose_name='R4 时间窗口(秒)')),
|
|
('r4_city_count', models.IntegerField(blank=True, null=True, verbose_name='R4 预期外城市数阈值')),
|
|
('r5_enabled', models.BooleanField(blank=True, null=True, verbose_name='R5 开关')),
|
|
('r5_days', models.IntegerField(blank=True, null=True, verbose_name='R5 统计天数')),
|
|
('r5_country_count', models.IntegerField(blank=True, null=True, verbose_name='R5 海外国家数阈值')),
|
|
('team', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='anomaly_config', to='accounts.team', verbose_name='团队')),
|
|
],
|
|
options={
|
|
'verbose_name': '团队异常检测配置',
|
|
'verbose_name_plural': '团队异常检测配置',
|
|
},
|
|
),
|
|
migrations.CreateModel(
|
|
name='LoginAnomaly',
|
|
fields=[
|
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
('level', models.CharField(choices=[('warning', '警告'), ('critical', '严重')], max_length=10, verbose_name='严重程度')),
|
|
('rule', models.CharField(choices=[('region_mismatch', '登录地区不对'), ('impossible_travel', '不可能的旅行'), ('login_frequency', '登录太频繁'), ('multi_city', '团队遍地开花'), ('overseas_ip_diversity', '海外IP太杂')], max_length=30, verbose_name='触发规则')),
|
|
('detail', models.JSONField(default=dict, verbose_name='详情')),
|
|
('alerted', models.BooleanField(default=False, verbose_name='已发告警')),
|
|
('auto_disabled', models.BooleanField(default=False, verbose_name='已自动封禁')),
|
|
('disabled_target', models.CharField(blank=True, default='', max_length=10, verbose_name='封禁对象')),
|
|
('created_at', models.DateTimeField(auto_now_add=True, db_index=True, verbose_name='创建时间')),
|
|
('login_record', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='anomalies', to='accounts.loginrecord', verbose_name='触发登录记录')),
|
|
('team', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='login_anomalies', to='accounts.team', verbose_name='团队')),
|
|
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='login_anomalies', to=settings.AUTH_USER_MODEL, verbose_name='用户')),
|
|
],
|
|
options={
|
|
'verbose_name': '登录异常',
|
|
'verbose_name_plural': '登录异常',
|
|
'ordering': ['-created_at'],
|
|
},
|
|
),
|
|
]
|