增加登录验证码操作,登录失败超过限制次数,锁定24小时

super_signature
nineven 5 years ago
parent fb99233ce5
commit d9b0d3718c
  1. 5
      fir_client/src/components/FirAppInfosBase.vue
  2. 11
      fir_client/src/components/FirAppInfosbaseinfo.vue
  3. 11
      fir_client/src/components/FirAppInfoscombo.vue
  4. 5
      fir_client/src/components/FirAppInfossecurity.vue
  5. 14
      fir_client/src/components/FirAppInfostimeline.vue
  6. 17
      fir_client/src/components/FirApps.vue
  7. 4
      fir_client/src/components/FirDownload.vue
  8. 53
      fir_client/src/components/FirLogin.vue
  9. 82
      fir_client/src/restful/index.js
  10. 5
      fir_ser/api/urls.py
  11. 28
      fir_ser/api/utils/storage/caches.py
  12. 15
      fir_ser/api/utils/storage/localApi.py
  13. 28
      fir_ser/api/utils/utils.py
  14. 131
      fir_ser/api/views/apps.py
  15. 18
      fir_ser/api/views/captcha.py
  16. 3
      fir_ser/api/views/download.py
  17. 31
      fir_ser/api/views/login.py
  18. 63
      fir_ser/fir_ser/settings.py
  19. 9
      fir_ser/fir_ser/urls.py
  20. 2
      fir_ser/nginx-vhost.conf
  21. 2
      fir_ser/requirements.txt

@ -64,7 +64,7 @@
</template> </template>
<script> <script>
import {getappinfos} from "../restful"; import {apputils} from "../restful";
export default { export default {
name: "FirAppInfosBase", name: "FirAppInfosBase",
@ -136,7 +136,7 @@
}, },
}, },
computed: {}, mounted() { computed: {}, mounted() {
getappinfos(data => { apputils(data => {
if (data.code === 1000) { if (data.code === 1000) {
this.appinfos = data.data; this.appinfos = data.data;
@ -151,6 +151,7 @@
console.log("失败了"); console.log("失败了");
} }
}, { }, {
"methods": "GET",
"app_id": this.$route.params.id "app_id": this.$route.params.id
}); });
if (this.$store.state.currentapp.master_release) { if (this.$store.state.currentapp.master_release) {

@ -58,7 +58,7 @@
</template> </template>
<script> <script>
import {deleteapp, getappinfos, getapppicurl, uploadimgs, updateapp, getuploadurl} from "../restful" import {apputils, getapppicurl, uploadimgs, getuploadurl} from "../restful"
import {uploadaliyunoss, uploadlocalstorage, uploadqiniuoss} from "../utils"; import {uploadaliyunoss, uploadlocalstorage, uploadqiniuoss} from "../utils";
export default { export default {
@ -122,7 +122,7 @@
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
.then(res => { .then(res => {
this.willDeleteApp = false; this.willDeleteApp = false;
deleteapp(data => { apputils(data => {
if (data.code === 1000) { if (data.code === 1000) {
this.$message({ this.$message({
message: '删除成功', message: '删除成功',
@ -136,6 +136,7 @@
}); });
} }
}, { }, {
"methods": "DELETE",
"app_id": this.currentapp.app_id "app_id": this.currentapp.app_id
}); });
@ -147,7 +148,7 @@
// alert('APP',this.delapp.name) // alert('APP',this.delapp.name)
}, },
updateinfo() { updateinfo() {
getappinfos(data => { apputils(data => {
if (data.code === 1000) { if (data.code === 1000) {
this.appinfos = data.data; this.appinfos = data.data;
@ -163,17 +164,19 @@
console.log("失败了"); console.log("失败了");
} }
}, { }, {
"methods": "GET",
"app_id": this.currentapp.app_id "app_id": this.currentapp.app_id
}); });
}, },
saveappinfo() { saveappinfo() {
updateapp(data => { apputils(data => {
if (data.code === 1000) { if (data.code === 1000) {
this.$message.success('数据更新成功'); this.$message.success('数据更新成功');
} else { } else {
this.$message.error('操作失败,' + data.msg); this.$message.error('操作失败,' + data.msg);
} }
}, { }, {
"methods": "PUT",
"app_id": this.currentapp.app_id, "app_id": this.currentapp.app_id,
"data": { "data": {
"description": this.currentapp.description, "description": this.currentapp.description,

@ -80,7 +80,7 @@
</template> </template>
<script> <script>
import {getappinfos, getapps, updateapp} from "../restful"; import {getapps, apputils} from "../restful";
export default { export default {
name: "FirAppInfoscombo" name: "FirAppInfoscombo"
@ -111,13 +111,13 @@
} }
}, },
each_add(hcombo_id) { each_add(hcombo_id) {
updateapp(data => { apputils(data => {
if (data.code === 1000) { if (data.code === 1000) {
this.$message.success('操作成功'); this.$message.success('操作成功');
this.comboapplists = []; this.comboapplists = [];
getappinfos(data => { apputils(data => {
let appinfos = {}; let appinfos = {};
appinfos = data.data; appinfos = data.data;
appinfos["icon_url"] = appinfos.master_release.icon_url; appinfos["icon_url"] = appinfos.master_release.icon_url;
@ -134,6 +134,7 @@
} }
}, { }, {
"methods": "GET",
"app_id": this.currentapp.app_id "app_id": this.currentapp.app_id
}); });
@ -142,6 +143,7 @@
} }
}, { }, {
"methods": "PUT",
"app_id": this.currentapp.app_id, "app_id": this.currentapp.app_id,
"data": { "data": {
"has_combo": { "has_combo": {
@ -152,7 +154,7 @@
}); });
}, },
each_confirm() { each_confirm() {
updateapp(data => { apputils(data => {
if (data.code === 1000) { if (data.code === 1000) {
this.$message.success('操作成功'); this.$message.success('操作成功');
@ -166,6 +168,7 @@
} }
}, { }, {
"methods": "PUT",
"app_id": this.currentapp.app_id, "app_id": this.currentapp.app_id,
"data": { "data": {
"has_combo": { "has_combo": {

@ -68,7 +68,7 @@
</template> </template>
<script> <script>
import {updateapp,} from "../restful" import {apputils,} from "../restful"
import {deepCopy} from "../utils"; import {deepCopy} from "../utils";
export default { export default {
@ -92,7 +92,7 @@
this.currentapp.count = 0; this.currentapp.count = 0;
}, },
saveappinfo(data) { saveappinfo(data) {
updateapp(data => { apputils(data => {
if (data.code === 1000) { if (data.code === 1000) {
this.$message.success('数据更新成功'); this.$message.success('数据更新成功');
} else { } else {
@ -106,6 +106,7 @@
} }
}, { }, {
"methods": "PUT",
"app_id": this.currentapp.app_id, "app_id": this.currentapp.app_id,
"data": data "data": data
}); });

@ -115,7 +115,7 @@
</template> </template>
<script> <script>
import {deletereleaseapp, getapptimeline, getdownloadurl, updatereleaseapp} from "../restful" import {releaseapputils, getdownloadurl} from "../restful"
export default { export default {
name: "FirAppInfostimeline", name: "FirAppInfostimeline",
@ -161,7 +161,7 @@
spinner: 'el-icon-loading', spinner: 'el-icon-loading',
// background: 'rgba(0, 0, 0, 0.7)' // background: 'rgba(0, 0, 0, 0.7)'
}); });
getapptimeline(data => { releaseapputils(data => {
if (data.code === 1000) { if (data.code === 1000) {
this.release_apps = data.data.release_apps; this.release_apps = data.data.release_apps;
this.currentapp = data.data.currentapp; this.currentapp = data.data.currentapp;
@ -171,8 +171,9 @@
} }
loading.close(); loading.close();
}, { }, {
"methods": "GET",
"app_id": this.$route.params.id, "app_id": this.$route.params.id,
"action": "timeline", "release_id": "timeline",
}) })
}, },
del_release_app(app) { del_release_app(app) {
@ -180,7 +181,7 @@
this.$confirm('确认删除 ' + this.currentapp.name + '下 当前 release 版本吗?') this.$confirm('确认删除 ' + this.currentapp.name + '下 当前 release 版本吗?')
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
.then(res => { .then(res => {
deletereleaseapp(data => { releaseapputils(data => {
if (data.code === 1000) { if (data.code === 1000) {
this.$message({ this.$message({
message: '删除成功', message: '删除成功',
@ -195,6 +196,7 @@
}); });
} }
}, { }, {
"methods": "DELETE",
"app_id": this.currentapp.app_id, "app_id": this.currentapp.app_id,
"release_id": app.release_id, "release_id": app.release_id,
}); });
@ -202,7 +204,7 @@
}, },
updatereleaseappFun(params) { updatereleaseappFun(params) {
updatereleaseapp(data => { releaseapputils(data => {
if (data.code === 1000) { if (data.code === 1000) {
this.$message({ this.$message({
message: '更新成功', message: '更新成功',
@ -225,6 +227,7 @@
}, },
make_master_release(app) { make_master_release(app) {
this.updatereleaseappFun({ this.updatereleaseappFun({
"methods": "PUT",
"app_id": this.currentapp.app_id, "app_id": this.currentapp.app_id,
"release_id": app.release_id, "release_id": app.release_id,
"data": { "data": {
@ -250,6 +253,7 @@
this.updatas = {"binary_url": app.binary_url} this.updatas = {"binary_url": app.binary_url}
} }
this.updatereleaseappFun({ this.updatereleaseappFun({
"methods": "PUT",
"app_id": this.currentapp.app_id, "app_id": this.currentapp.app_id,
"release_id": app.release_id, "release_id": app.release_id,
"data": this.updatas "data": this.updatas

@ -185,19 +185,19 @@
placeholder="请输入名称搜索" placeholder="请输入名称搜索"
v-model="keysearch" v-model="keysearch"
@click="searchapps" @click="searchapps"
clearable > clearable>
</el-input> </el-input>
</el-col> </el-col>
<el-col :span="2" > <el-col :span="2">
<el-button icon="el-icon-search" @click="searchFun"> <el-button icon="el-icon-search" @click="searchFun">
</el-button> </el-button>
</el-col> </el-col>
</el-row> </el-row>
</el-col> </el-col>
<!-- <el-col :span="1" class="surplus-card">--> <!-- <el-col :span="1" class="surplus-card">-->
<!-- --> <!-- -->
<!-- </el-col>--> <!-- </el-col>-->
<el-col :span="7" class="surplus-card"> <el-col :span="7" class="surplus-card">
<el-row> <el-row>
@ -405,7 +405,7 @@
</template> </template>
<script> <script>
import {getapps, deleteapp, analyseApps, getuploadurl} from "../restful"; import {getapps, apputils, analyseApps, getuploadurl} from "../restful";
import { import {
getScrollHeight, getScrollHeight,
getScrollTop, getScrollTop,
@ -698,7 +698,7 @@
}, },
delApp() { delApp() {
this.willDeleteApp = false; this.willDeleteApp = false;
deleteapp(data => { apputils(data => {
if (data.code === 1000) { if (data.code === 1000) {
for (let i = 0; i < this.applists.length; i++) { for (let i = 0; i < this.applists.length; i++) {
if (this.delapp.app_id === this.applists[i].app_id) { if (this.delapp.app_id === this.applists[i].app_id) {
@ -712,6 +712,7 @@
this.$message.error('删除失败,请联系管理员'); this.$message.error('删除失败,请联系管理员');
} }
}, { }, {
"methods": "DELETE",
"app_id": this.delapp.app_id "app_id": this.delapp.app_id
}); });
}, },
@ -794,7 +795,7 @@
keysearch: function (val, oldVal) { keysearch: function (val, oldVal) {
// this.searchapps() // this.searchapps()
let keysearch = this.keysearch.replace(/^\s+|\s+$/g, ""); let keysearch = this.keysearch.replace(/^\s+|\s+$/g, "");
if(keysearch === ""){ if (keysearch === "") {
this.searchFun() this.searchFun()
} }
}, },

@ -13,8 +13,8 @@
</div> </div>
<div v-else> <div v-else>
<!-- <span class="pattern left"><img src="../assets/download_pattern_left.png"></span>--> <!-- <span class="pattern left"><img src="../assets/download_pattern_left.png"></span>-->
<!-- <span class="pattern right"><img src="../assets/download_pattern_right.png"></span>--> <!-- <span class="pattern right"><img src="../assets/download_pattern_right.png"></span>-->
</div> </div>
<el-container class="out-container"> <el-container class="out-container">

@ -24,6 +24,20 @@
<el-input v-model="form.password" prefix-icon="el-icon-lock" placeholder="密码" <el-input v-model="form.password" prefix-icon="el-icon-lock" placeholder="密码"
show-password></el-input> show-password></el-input>
</el-form-item> </el-form-item>
<el-form-item style="height: 40px">
<el-row style="height: 40px">
<el-col :span="16">
<el-input placeholder="请输入验证码" v-model="form.authcode" maxlength="6"></el-input>
</el-col>
<el-col :span="8">
<el-image
style="margin:0px 4px;border-radius:4px;cursor:pointer;height: 40px"
:src="cptch.cptch_image"
fit="contain" @click="get_auth_code">
</el-image>
</el-col>
</el-row>
</el-form-item>
<el-form-item> <el-form-item>
@ -53,17 +67,18 @@
form: { form: {
email: '', email: '',
password: '', password: '',
authcode:''
}, },
cptch:{"cptch_image": '', "cptch_key": '',"length":8},
} }
}, },
methods: { methods: {
onSubmit() { onSubmit() {
let email = this.form.email; let email = this.form.email;
let password = this.form.password; let password = this.form.password;
let authcode = this.form.authcode;
if(authcode.length === this.cptch.length){
if (this.isEmail(email) && password.length > 6) { if (this.isEmail(email) && password.length > 6) {
// alert(email,password);
loginFun(data => { loginFun(data => {
if (data.code == 1000) { if (data.code == 1000) {
this.$message({ this.$message({
@ -82,10 +97,16 @@
message: data.msg, message: data.msg,
type: 'error' type: 'error'
}); });
this.get_auth_code();
} }
}, { }, {
"methods":"POST",
"data":{
"username": email, "username": email,
"password": password "password": password,
"authcode":authcode,
"cptch_key":this.cptch.cptch_key
}
}); });
@ -95,7 +116,12 @@
type: 'warning' type: 'warning'
}); });
} }
}else {
this.$message({
message: '验证码有误',
type: 'warning'
});
}
}, },
onRegister() { onRegister() {
this.$router.push({name: 'FirRegist'}) this.$router.push({name: 'FirRegist'})
@ -105,10 +131,25 @@
return true; return true;
} }
return false; return false;
},
get_auth_code(){
loginFun(data => {
if (data.code == 1000) {
this.cptch=data.data;
} else {
this.$message({
message: data.msg,
type: 'error'
});
} }
}, {
"methods":"GET",
"data":{}
});
},
}, },
mounted() { mounted() {
this.get_auth_code();
}, created() { }, created() {
} }
} }

@ -88,7 +88,7 @@ function ErrorMsg(error) {
} }
if (error.response && error.response.status === 403) { if (error.response && error.response.status === 403) {
router.push({name: 'FirLogin'}); router.push({name: 'FirLogin'});
}else{ } else {
if (error.message === 'Network Error') { if (error.message === 'Network Error') {
alert('网络连接失败'); alert('网络连接失败');
} else { } else {
@ -161,9 +161,9 @@ function getData(methods = true, url, params = {}, callBack, load, isCode = fals
/**用户登录 */ /**用户登录 */
export function loginFun(callBack, params, load = true) { export function loginFun(callBack, params, load = true) {
getData( getData(
'POST', params.methods,
USERSEVER + '/login', USERSEVER + '/login',
params, params.data,
data => { data => {
callBack(data); callBack(data);
}, },
@ -204,58 +204,11 @@ export function getapps(callBack, params, load = true) {
); );
} }
/**用户应用详情 */
export function getappinfos(callBack, params, load = true) {
getData(
'GET',
USERSEVER + '/apps/' + params.app_id,
{},
data => {
callBack(data);
},
load,
true,
true
);
}
/**删除应用 */
export function deleteapp(callBack, params, load = true) {
getData(
"DELETE",
USERSEVER + '/apps/' + params.app_id,
{},
data => {
callBack(data);
},
load,
true,
true
);
}
/**删除release应用 */ /**app应用操作 */
export function deletereleaseapp(callBack, params, load = true) { export function apputils(callBack, params, load = true) {
getData( getData(
"DELETE", params.methods,
USERSEVER + '/appinfos/' + params.app_id + '/' + params.release_id,
{},
data => {
callBack(data);
},
load,
true,
true
);
}
/**更新app应用 */
export function updateapp(callBack, params, load = true) {
getData(
"PUT",
USERSEVER + '/apps/' + params.app_id, USERSEVER + '/apps/' + params.app_id,
params.data, params.data,
data => { data => {
@ -267,11 +220,10 @@ export function updateapp(callBack, params, load = true) {
); );
} }
/**release应用操作 */
/**更新release应用 */ export function releaseapputils(callBack, params, load = true) {
export function updatereleaseapp(callBack, params, load = true) {
getData( getData(
"PUT", params.methods,
USERSEVER + '/appinfos/' + params.app_id + '/' + params.release_id, USERSEVER + '/appinfos/' + params.app_id + '/' + params.release_id,
params.data, params.data,
data => { data => {
@ -284,22 +236,6 @@ export function updatereleaseapp(callBack, params, load = true) {
} }
/**用户应用详情 */
export function getapptimeline(callBack, params, load = true) {
getData(
'GET',
USERSEVER + '/appinfos/' + params.app_id + '/' + params.action,
{},
data => {
callBack(data);
},
load,
true,
true
);
}
/**根据短链接获取应用信息 */ /**根据短链接获取应用信息 */
export function getShortAppinfo(callBack, params, load = true) { export function getShortAppinfo(callBack, params, load = true) {
getData( getData(

@ -18,7 +18,7 @@ from django.urls import path, re_path, include
from api.views.login import LoginView, UserInfoView from api.views.login import LoginView, UserInfoView
from api.views.logout import LogoutView from api.views.logout import LogoutView
from api.views.captcha import CaptchaView from api.views.captcha import AjaxExampleForm
from api.views.apps import AppsView, AppInfoView, AppReleaseinfoView from api.views.apps import AppsView, AppInfoView, AppReleaseinfoView
from api.views.download import ShortDownloadView from api.views.download import ShortDownloadView
from api.views.uploads import AppAnalyseView, UploadView from api.views.uploads import AppAnalyseView, UploadView
@ -34,7 +34,7 @@ urlpatterns = [
# path("",include(router.urls)), # path("",include(router.urls)),
re_path("^login", LoginView.as_view()), re_path("^login", LoginView.as_view()),
re_path("^logout", LogoutView.as_view()), re_path("^logout", LogoutView.as_view()),
re_path("^captcha_check/", CaptchaView.as_view()), re_path("^captcha_check/", AjaxExampleForm.as_view()),
re_path("^apps$", AppsView.as_view()), re_path("^apps$", AppsView.as_view()),
re_path("^storage$", StorageView.as_view()), re_path("^storage$", StorageView.as_view()),
re_path("^apps/(?P<app_id>\w+)", AppInfoView.as_view()), re_path("^apps/(?P<app_id>\w+)", AppInfoView.as_view()),
@ -47,4 +47,5 @@ urlpatterns = [
re_path("^supersign/developer$", DeveloperView.as_view()), re_path("^supersign/developer$", DeveloperView.as_view()),
re_path("^supersign/devices$", SuperSignUsedView.as_view()), re_path("^supersign/devices$", SuperSignUsedView.as_view()),
re_path("^supersign/udid$", AppUDIDUsedView.as_view()), re_path("^supersign/udid$", AppUDIDUsedView.as_view()),
] ]

@ -8,11 +8,13 @@ from django.core.cache import cache
from api.models import Apps, UserInfo, AppReleaseInfo, AppUDID, APPToDeveloper, APPSuperSignUsedInfo from api.models import Apps, UserInfo, AppReleaseInfo, AppUDID, APPToDeveloper, APPSuperSignUsedInfo
import time, os import time, os
from django.utils import timezone from django.utils import timezone
from fir_ser.settings import CACHE_KEY_TEMPLATE, SERVER_DOMAIN from fir_ser.settings import CACHE_KEY_TEMPLATE, SERVER_DOMAIN, SYNC_CACHE_TO_DATABASE
from api.utils.storage.storage import Storage, LocalStorage from api.utils.storage.storage import Storage, LocalStorage
from api.utils.crontab.sync_cache import sync_download_times_by_app_id from api.utils.crontab.sync_cache import sync_download_times_by_app_id
from api.utils.utils import file_format_path from api.utils.utils import file_format_path
import logging
logger = logging.getLogger(__file__)
try: try:
from api.utils.crontab import run from api.utils.crontab import run
except Exception as e: except Exception as e:
@ -180,3 +182,27 @@ def upload_file_tmp_name(act, filename, user_obj_id):
return cache.get(tmp_key) return cache.get(tmp_key)
elif act == "del": elif act == "del":
cache.delete(tmp_key) cache.delete(tmp_key)
def login_auth_failed(act, email):
auth_code_key = "_".join([CACHE_KEY_TEMPLATE.get("login_failed_try_times_key"), email])
if act == "set":
data = {
"count": 1,
"time": time.time()
}
cdata = cache.get(auth_code_key)
if cdata:
data["count"] = cdata["count"] + 1
data["time"] = time.time()
logger.info("auth_code_key:%s data:%s" % (auth_code_key, data))
cache.set(auth_code_key, data, 60 * 60)
elif act == "get":
cdata = cache.get(auth_code_key)
if cdata:
if cdata["count"] > SYNC_CACHE_TO_DATABASE.get("try_login_times"):
logging.error("email:%s login failed too many ,is locked . cdata:%s" % (email, cdata))
return False
return True
elif act == "del":
cache.delete(auth_code_key)

@ -20,17 +20,18 @@ class LocalStorage(object):
dtoken = DownloadToken() dtoken = DownloadToken()
return dtoken.make_token(name, expires) return dtoken.make_token(name, expires)
def get_download_url(self, name, expires=600, force_new=False): def get_base_url(self):
dtoken = DownloadToken()
base_url = '/'.join([self.domain_name, 'download', name])
uri = 'http://' uri = 'http://'
if self.is_https: if self.is_https:
uri = 'https://' uri = 'https://'
return "%s%s" % (uri, self.domain_name)
def get_download_url(self, name, expires=600, force_new=False):
dtoken = DownloadToken()
download_url = '/'.join([self.get_base_url(), 'download', name])
if settings.DATA_DOWNLOAD_KEY_OPEN: if settings.DATA_DOWNLOAD_KEY_OPEN:
download_url = "%s%s?%s=%s" % ( download_url = "%s?%s=%s" % (
uri, base_url, settings.DATA_DOWNLOAD_KEY, dtoken.make_token(name, expires, force_new=force_new)) download_url, settings.DATA_DOWNLOAD_KEY, dtoken.make_token(name, expires, force_new=force_new))
else:
download_url = "%s%s" % (uri, base_url)
return download_url return download_url
def del_file(self, name): def del_file(self, name):

@ -4,12 +4,17 @@
# author: liuyu # author: liuyu
# date: 2020/5/7 # date: 2020/5/7
import os import os
from fir_ser.settings import SUPER_SIGN_ROOT from fir_ser.settings import SUPER_SIGN_ROOT, SERVER_DOMAIN, CAPTCHA_LENGTH
from api.models import APPSuperSignUsedInfo, AppUDID, AppIOSDeveloperInfo, AppReleaseInfo, Apps, APPToDeveloper, \ from api.models import APPSuperSignUsedInfo, APPToDeveloper, \
UDIDsyncDeveloper UDIDsyncDeveloper
from api.utils.app.randomstrings import make_app_uuid from api.utils.app.randomstrings import make_app_uuid
from api.utils.storage.localApi import LocalStorage from api.utils.storage.localApi import LocalStorage
from django.db.models import Sum from django.db.models import Sum
from captcha.models import CaptchaStore
from captcha.helpers import captcha_image_url
import logging
logger = logging.getLogger(__name__)
def file_format_path(user_obj, auth=None, email=None): def file_format_path(user_obj, auth=None, email=None):
@ -77,3 +82,22 @@ def get_developer_devices(developer_obj_lists):
"flyapp_used_sum": flyapp_used_sum, "flyapp_used_sum": flyapp_used_sum,
} }
return use_num return use_num
def get_captcha():
cptch_key = CaptchaStore.generate_key()
cptch_image = captcha_image_url(cptch_key)
CaptchaStore.remove_expired()
local_storage = LocalStorage(**SERVER_DOMAIN.get("IOS_PMFILE_DOWNLOAD_DOMAIN"))
return {"cptch_image": "/".join([local_storage.get_base_url(), cptch_image.strip("/")]), "cptch_key": cptch_key,
"length": CAPTCHA_LENGTH}
def valid_captcha(cptch_key, code, username):
if username:
challenge = CaptchaStore.objects.filter(hashkey=cptch_key).values("challenge").first()
logger.info("cptch_key:%s code:%s challenge:%s" % ((cptch_key, code, challenge)))
if challenge:
if cptch_key and code and code.strip(" ").lower() == challenge.get("challenge").lower():
return True
return False

@ -18,6 +18,9 @@ from api.utils.storage.caches import del_cache_response_by_short, get_app_today_
from api.models import Apps, AppReleaseInfo, APPToDeveloper, AppIOSDeveloperInfo, UserInfo from api.models import Apps, AppReleaseInfo, APPToDeveloper, AppIOSDeveloperInfo, UserInfo
from api.utils.serializer import AppsSerializer, AppReleaseSerializer, UserInfoSerializer from api.utils.serializer import AppsSerializer, AppReleaseSerializer, UserInfoSerializer
from rest_framework.pagination import PageNumberPagination from rest_framework.pagination import PageNumberPagination
import logging
logger = logging.getLogger(__name__)
class AppsPageNumber(PageNumberPagination): class AppsPageNumber(PageNumberPagination):
@ -34,7 +37,7 @@ class AppsView(APIView):
app_type = request.query_params.get("type", None) app_type = request.query_params.get("type", None)
act_type = request.query_params.get("act", None) act_type = request.query_params.get("act", None)
logger.info("app_type:%s act_type:%s" % (app_type, act_type))
res = BaseResponse() res = BaseResponse()
res.hdata = {"all_hits_count": 0} res.hdata = {"all_hits_count": 0}
res.hdata["upload_domain"] = request.user.domain_name res.hdata["upload_domain"] = request.user.domain_name
@ -52,9 +55,11 @@ class AppsView(APIView):
count_hits = all_hits_obj.get("count_hits", 0) count_hits = all_hits_obj.get("count_hits", 0)
if count_hits: if count_hits:
if count_hits > 0: if count_hits > 0:
request.user.all_download_times += count_hits logger.info(
"update user all_download_times old:%s now:%s" % (
count_hits, request.user.all_download_times))
UserInfo.objects.filter(pk=request.user.id).update( UserInfo.objects.filter(pk=request.user.id).update(
all_download_times=request.user.all_download_times + count_hits) all_download_times=count_hits)
else: else:
count_hits = 0 count_hits = 0
res.hdata["all_hits_count"] = count_hits res.hdata["all_hits_count"] = count_hits
@ -73,7 +78,7 @@ class AppsView(APIView):
filter_data["has_combo"] = None filter_data["has_combo"] = None
apps_obj = Apps.objects.filter(**filter_data) apps_obj = Apps.objects.filter(**filter_data)
logger.info(filter_data)
page_obj = AppsPageNumber() page_obj = AppsPageNumber()
app_page_serializer = page_obj.paginate_queryset(queryset=apps_obj.order_by("-updated_time"), request=request, app_page_serializer = page_obj.paginate_queryset(queryset=apps_obj.order_by("-updated_time"), request=request,
view=self) view=self)
@ -100,6 +105,7 @@ class AppInfoView(APIView):
app_serializer = AppsSerializer(apps_obj, context={"storage": Storage(request.user)}) app_serializer = AppsSerializer(apps_obj, context={"storage": Storage(request.user)})
res.data = app_serializer.data res.data = app_serializer.data
else: else:
logger.error("app_id:%s is not found in user:%s " % (app_id, request.user))
res.msg = "未找到该应用" res.msg = "未找到该应用"
res.code = 1003 res.code = 1003
userserializer = UserInfoSerializer(request.user) userserializer = UserInfoSerializer(request.user)
@ -116,15 +122,20 @@ class AppInfoView(APIView):
apps_obj = Apps.objects.filter(user_id=request.user, app_id=app_id).first() apps_obj = Apps.objects.filter(user_id=request.user, app_id=app_id).first()
if apps_obj: if apps_obj:
if apps_obj.issupersign: if apps_obj.issupersign:
logger.info("app_id:%s is supersign ,so delete this app need clean IOS developer")
IosUtils.clean_app_by_user_obj(apps_obj, request.user) IosUtils.clean_app_by_user_obj(apps_obj, request.user)
storage = Storage(request.user) storage = Storage(request.user)
has_combo = apps_obj.has_combo has_combo = apps_obj.has_combo
if has_combo: if has_combo:
logger.info(
"app_id:%s has_combo ,so delete this app need uncombo and clean del_cache_response_by_short")
del_cache_response_by_short(apps_obj.has_combo.short, apps_obj.has_combo.app_id) del_cache_response_by_short(apps_obj.has_combo.short, apps_obj.has_combo.app_id)
apps_obj.has_combo.has_combo = None apps_obj.has_combo.has_combo = None
del_cache_response_by_short(apps_obj.short, apps_obj.app_id) del_cache_response_by_short(apps_obj.short, apps_obj.app_id)
for appreleaseobj in AppReleaseInfo.objects.filter(app_id=apps_obj).all(): for appreleaseobj in AppReleaseInfo.objects.filter(app_id=apps_obj).all():
logger.info("app_id:%s has_combo ,so delete this app need clean all release,release_id:%s" % (
appreleaseobj.release_id))
storage.delete_file(appreleaseobj.release_id, appreleaseobj.release_type) storage.delete_file(appreleaseobj.release_id, appreleaseobj.release_type)
storage.delete_file(appreleaseobj.icon_url) storage.delete_file(appreleaseobj.icon_url)
appreleaseobj.delete() appreleaseobj.delete()
@ -139,6 +150,7 @@ class AppInfoView(APIView):
clean = data.get("clean", None) clean = data.get("clean", None)
if clean: if clean:
logger.info("app_id:%s clean:%s ,close supersign should clean_app_by_user_obj" % (app_id, clean))
apps_obj = Apps.objects.filter(user_id=request.user, app_id=app_id).first() apps_obj = Apps.objects.filter(user_id=request.user, app_id=app_id).first()
IosUtils.clean_app_by_user_obj(apps_obj, request.user) IosUtils.clean_app_by_user_obj(apps_obj, request.user)
return Response(res.dict) return Response(res.dict)
@ -147,6 +159,7 @@ class AppInfoView(APIView):
if has_combo: if has_combo:
actions = has_combo.get("action", None) actions = has_combo.get("action", None)
hcombo_id = has_combo.get("hcombo_id", None) hcombo_id = has_combo.get("hcombo_id", None)
logger.info("app_id:%s actions:%s hcombo_id:%s" % (app_id, actions, hcombo_id))
if actions and hcombo_id: if actions and hcombo_id:
has_combo = Apps.objects.filter(user_id=request.user, app_id=hcombo_id) has_combo = Apps.objects.filter(user_id=request.user, app_id=hcombo_id)
@ -166,6 +179,7 @@ class AppInfoView(APIView):
del_cache_response_by_short(has_combo.first().short, has_combo.first().app_id) del_cache_response_by_short(has_combo.first().short, has_combo.first().app_id)
except Exception as e: except Exception as e:
logger.error("app_id:%s actions:%s hcombo_id:%s Exception:%s" % (app_id, actions, hcombo_id, e))
res.code = 1004 res.code = 1004
res.msg = "该应用已经关联" res.msg = "该应用已经关联"
else: else:
@ -187,71 +201,72 @@ class AppInfoView(APIView):
# return Response(res.dict) # return Response(res.dict)
developer_count = AppIOSDeveloperInfo.objects.filter(user_id=request.user).count() developer_count = AppIOSDeveloperInfo.objects.filter(user_id=request.user).count()
if developer_count == 0: if developer_count == 0:
logger.error("app_id:%s can't open supersign,owner has no ios developer" % (app_id))
res.code = 1008 res.code = 1008
res.msg = "超级签开发者不存在,无法开启" res.msg = "超级签开发者不存在,无法开启"
return Response(res.dict) return Response(res.dict)
apps_obj.issupersign = data.get("issupersign", apps_obj.issupersign) apps_obj.issupersign = data.get("issupersign", apps_obj.issupersign)
apps_obj.save() apps_obj.save()
except Exception as e: except Exception as e:
print(e) logger.error("app_id:%s update Exception:%s" % (app_id, e))
res.code = 1005 res.code = 1005
res.msg = "短连接已经存在" res.msg = "短连接已经存在"
return Response(res.dict) return Response(res.dict)
def post(self, request, app_id): # def post(self, request, app_id):
res = BaseResponse() # res = BaseResponse()
#
# 获取多个file # # 获取多个file
files = request.FILES.getlist('file', None) # files = request.FILES.getlist('file', None)
for file_obj in files: # for file_obj in files:
# 将文件缓存到本地后上传 # # 将文件缓存到本地后上传
try: # try:
app_type = file_obj.name.split(".")[-1] # app_type = file_obj.name.split(".")[-1]
if app_type in ['png', 'jpeg', 'jpg']: # if app_type in ['png', 'jpeg', 'jpg']:
# 上传图片 # # 上传图片
pass # pass
else: # else:
raise # raise
except Exception as e: # except Exception as e:
res.code = 1003 # res.code = 1003
res.msg = "错误的类型" # res.msg = "错误的类型"
return Response(res.dict) # return Response(res.dict)
#
if app_id: # if app_id:
apps_obj = Apps.objects.filter(user_id=request.user, app_id=app_id).first() # apps_obj = Apps.objects.filter(user_id=request.user, app_id=app_id).first()
if apps_obj: # if apps_obj:
release_obj = AppReleaseInfo.objects.filter(app_id=apps_obj, is_master=True).first() # release_obj = AppReleaseInfo.objects.filter(app_id=apps_obj, is_master=True).first()
if release_obj: # if release_obj:
#
random_file_name = make_from_user_uuid(request.user) # random_file_name = make_from_user_uuid(request.user)
old_file_name = os.path.basename(release_obj.icon_url) # old_file_name = os.path.basename(release_obj.icon_url)
local_file = os.path.join(settings.MEDIA_ROOT, "icons", # local_file = os.path.join(settings.MEDIA_ROOT, "icons",
random_file_name + "." + old_file_name.split(".")[1]) # random_file_name + "." + old_file_name.split(".")[1])
# 读取传入的文件 # # 读取传入的文件
try: # try:
destination = open(local_file, 'wb+') # destination = open(local_file, 'wb+')
for chunk in file_obj.chunks(): # for chunk in file_obj.chunks():
# 写入本地文件 # # 写入本地文件
destination.write(chunk) # destination.write(chunk)
destination.close() # destination.close()
except Exception as e: # except Exception as e:
res.code = 1003 # res.code = 1003
res.msg = "数据写入失败" # res.msg = "数据写入失败"
return Response(res.dict) # return Response(res.dict)
release_obj.icon_url = release_obj.icon_url.replace(old_file_name.split(".")[0], # release_obj.icon_url = release_obj.icon_url.replace(old_file_name.split(".")[0],
random_file_name) # random_file_name)
release_obj.save() # release_obj.save()
del_cache_response_by_short(apps_obj.short, apps_obj.app_id) # del_cache_response_by_short(apps_obj.short, apps_obj.app_id)
#
storage = Storage(request.user) # storage = Storage(request.user)
storage.delete_file(old_file_name) # storage.delete_file(old_file_name)
#
else: # else:
res.code = 1003 # res.code = 1003
else: # else:
res.code = 1003 # res.code = 1003
return Response(res.dict) # return Response(res.dict)
class AppReleaseinfoView(APIView): class AppReleaseinfoView(APIView):

@ -16,3 +16,21 @@ class CaptchaView(APIView):
response_str = json.loads(response_str) response_str = json.loads(response_str)
return Response({"error_no": 0, "data": response_str}) return Response({"error_no": 0, "data": response_str})
from captcha.models import CaptchaStore
from captcha.helpers import captcha_image_url
import json
class AjaxExampleForm(APIView):
def get(self, request):
to_json_response = dict()
to_json_response['status'] = 0
to_json_response['new_cptch_key'] = CaptchaStore.generate_key()
to_json_response['new_cptch_image'] = captcha_image_url(to_json_response['new_cptch_key'])
CaptchaStore.remove_expired()
a = CaptchaStore.objects.filter(hashkey=to_json_response['new_cptch_key']).first()
print(a)
return Response(to_json_response)

@ -78,7 +78,10 @@ class DownloadView(APIView):
else: else:
file_path = os.path.join(settings.MEDIA_ROOT, filename) file_path = os.path.join(settings.MEDIA_ROOT, filename)
try: try:
if os.path.isfile(file_path):
response = FileResponse(open(file_path, 'rb')) response = FileResponse(open(file_path, 'rb'))
else:
response = FileResponse()
except Exception as e: except Exception as e:
print(e) print(e)
response = FileResponse() response = FileResponse()

@ -6,11 +6,16 @@ from django.core.cache import cache
from rest_framework.views import APIView from rest_framework.views import APIView
import binascii import binascii
import os, datetime import os, datetime
from api.utils.utils import get_captcha, valid_captcha
from api.utils.TokenManager import DownloadToken, generateNumericTokenOfLength from api.utils.TokenManager import DownloadToken, generateNumericTokenOfLength
from api.utils.auth import ExpiringTokenAuthentication from api.utils.auth import ExpiringTokenAuthentication
from api.utils.response import BaseResponse from api.utils.response import BaseResponse
from django.middleware import csrf from django.middleware import csrf
from fir_ser.settings import CACHE_KEY_TEMPLATE from fir_ser.settings import CACHE_KEY_TEMPLATE
from api.utils.storage.caches import login_auth_failed
import logging
logger = logging.getLogger(__name__)
def get_token(request): def get_token(request):
@ -27,18 +32,16 @@ class LoginView(APIView):
receive = request.data receive = request.data
if request.method == 'POST': if request.method == 'POST':
# print(receive) username = receive.get("username", None)
# is_valid = verify(receive) is_valid = valid_captcha(receive.get("cptch_key", None), receive.get("authcode", None), username)
is_valid = True
# print("is_valid", is_valid)
if is_valid: if is_valid:
username = receive.get("username") if login_auth_failed("get", username):
password = receive.get("password") password = receive.get("password")
user = auth.authenticate(username=username, password=password) user = auth.authenticate(username=username, password=password)
print(username, password, user) logger.info("username:%s password:%s" % (username, password))
if user is not None: if user is not None:
if user.is_active: if user.is_active:
login_auth_failed("del", username)
# update the token # update the token
key = self.generate_key() key = self.generate_key()
now = datetime.datetime.now() now = datetime.datetime.now()
@ -55,26 +58,28 @@ class LoginView(APIView):
response.token = key response.token = key
else: else:
response.msg = "用户被禁用" response.msg = "用户被禁用"
response.code = 1002 response.code = 1005
else: else:
login_auth_failed("set", username)
try: try:
UserInfo.objects.get(username=username) UserInfo.objects.get(username=username)
response.msg = "密码错误或者!" response.msg = "密码或者账户有误"
response.code = 1002 response.code = 1002
except UserInfo.DoesNotExist: except UserInfo.DoesNotExist:
response.msg = "用户不存在!" response.msg = "用户不存在!"
response.code = 1003 response.code = 1003
else: else:
response.code = 1006
response.msg = "用户登录失败次数过多,已被锁定,请24小时之后再次尝试"
else:
response.code = 1001 response.code = 1001
response.msg = "请完成滑动验证!" response.msg = "验证码有误"
return Response(response.dict) return Response(response.dict)
def get(self, request): def get(self, request):
response = BaseResponse() response = BaseResponse()
csrf = get_token(request) response.data = get_captcha()
response.data = csrf
return Response(response.dict) return Response(response.dict)

@ -38,6 +38,7 @@ INSTALLED_APPS = [
'api.apps.ApiConfig', 'api.apps.ApiConfig',
'django_apscheduler', # 定时执行任务 'django_apscheduler', # 定时执行任务
'rest_framework', 'rest_framework',
'captcha',
] ]
MIDDLEWARE = [ MIDDLEWARE = [
@ -236,6 +237,7 @@ CACHE_KEY_TEMPLATE = {
'download_today_times_key': 'download_today_times', 'download_today_times_key': 'download_today_times',
'developer_auth_code_key': 'developer_auth_code', 'developer_auth_code_key': 'developer_auth_code',
'upload_file_tmp_name_key': 'upload_file_tmp_name', 'upload_file_tmp_name_key': 'upload_file_tmp_name',
'login_failed_try_times_key': 'login_failed_try_times',
} }
DATA_DOWNLOAD_KEY = "d_token" DATA_DOWNLOAD_KEY = "d_token"
@ -246,6 +248,7 @@ SYNC_CACHE_TO_DATABASE = {
'download_times': 10, # 下载次数同步时间 'download_times': 10, # 下载次数同步时间
'auto_clean_tmp_file_times': 60 * 30, # 定时清理上传失误生成的临时文件 'auto_clean_tmp_file_times': 60 * 30, # 定时清理上传失误生成的临时文件
'auto_clean_apscheduler_log': 100000, # 定时清理定时任务执行的日志,该参数为日志保留的数量 'auto_clean_apscheduler_log': 100000, # 定时清理定时任务执行的日志,该参数为日志保留的数量
'try_login_times': 5, # 当天登录失败次数,超过该失败次数,锁定24小时
} }
SERVER_DOMAIN = { SERVER_DOMAIN = {
@ -259,3 +262,63 @@ SERVER_DOMAIN = {
SESSION_ENGINE = "django.contrib.sessions.backends.cache" SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "default" SESSION_CACHE_ALIAS = "default"
CAPTCHA_TIMEOUT=5 # Minutes
CAPTCHA_LENGTH= 6 # Chars
BASE_LOG_DIR = os.path.join(BASE_DIR, "logs")
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'standard': {
'format': '[%(asctime)s][%(threadName)s:%(thread)d][task_id:%(name)s][%(filename)s:%(lineno)d]'
'[%(levelname)s][%(message)s]'
},
'simple': {
'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s'
},
'collect': {
'format': '%(message)s'
}
},
'filters': {
'require_debug_true': {
'()': 'django.utils.log.RequireDebugTrue',
},
},
'handlers': {
'console': {
'level': 'DEBUG',
'filters': ['require_debug_true'], # 只有在Django debug为True时才在屏幕打印日志
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
'TF': {
'level': 'INFO',
'class': 'logging.handlers.TimedRotatingFileHandler', # 保存到文件,根据时间自动切
'filename': os.path.join(BASE_LOG_DIR, "flyapp_info.log"), # 日志文件
'backupCount': 10, # 备份数为3 xx.log --> xx.log.2018-08-23_00-00-00 --> xx.log.2018-08-24_00-00-00 --> ...
'when': 'D', # 每天一切, 可选值有S/秒 M/分 H/小时 D/天 W0-W6/周(0=周一) midnight/如果没指定时间就默认在午夜
'formatter': 'standard',
'encoding': 'utf-8',
},
'error': {
'level': 'ERROR',
'class': 'logging.handlers.RotatingFileHandler', # 保存到文件,自动切
'filename': os.path.join(BASE_LOG_DIR, "flyapp_err.log"), # 日志文件
'maxBytes': 1024 * 1024 * 5, # 日志大小 50M
'backupCount': 10,
'formatter': 'standard',
'encoding': 'utf-8',
},
},
'loggers': {
'': { # 默认的logger应用如下配置
'handlers': ['TF', 'console', 'error'], # 上线之后可以把'console'移除
'level': 'DEBUG',
'propagate': True,
},
},
}

@ -36,10 +36,10 @@ from api.views.download import DownloadView, InstallView
from api.views.receiveudids import IosUDIDView from api.views.receiveudids import IosUDIDView
urlpatterns = [ urlpatterns = [
path('fly.admin/', admin.site.urls), re_path('fly.admin/', admin.site.urls),
# path("",include(router.urls)), # path("",include(router.urls)),
path("api/v1/fir/server/", include('api.urls')), re_path("api/v1/fir/server/", include('api.urls')),
re_path('^captcha/', include('captcha.urls')),
# re_path("shoppingcar/",ShoppingCarView.as_view()), # re_path("shoppingcar/",ShoppingCarView.as_view()),
# re_path("account/",AccountView.as_view()), # re_path("account/",AccountView.as_view()),
# re_path("payment/",PaymentView.as_view()), # re_path("payment/",PaymentView.as_view()),
@ -47,8 +47,9 @@ urlpatterns = [
# re_path("get_pay_url/",get_pay_url),s # re_path("get_pay_url/",get_pay_url),s
# re_path("api/v1/trade/alipay/",AlipayTradeView), # re_path("api/v1/trade/alipay/",AlipayTradeView),
# media路径配置 # media路径配置
re_path(r'files/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}), re_path('files/(?P<path>.*)$', serve, {'document_root': settings.MEDIA_ROOT}),
re_path("download/(?P<filename>\w+\.\w+)$", DownloadView.as_view(), name="download"), re_path("download/(?P<filename>\w+\.\w+)$", DownloadView.as_view(), name="download"),
re_path("install/(?P<app_id>\w+)$", InstallView.as_view(), name="install"), re_path("install/(?P<app_id>\w+)$", InstallView.as_view(), name="install"),
re_path("^udid/(?P<short>\w+)$", IosUDIDView.as_view()), re_path("^udid/(?P<short>\w+)$", IosUDIDView.as_view()),
] ]

@ -40,7 +40,7 @@ server
location ~ ^/(download|api|files|udid) { location ~ ^/(download|api|files|udid|captcha) {
#proxy_pass http://synchrotron; #proxy_pass http://synchrotron;
include uwsgi_params; include uwsgi_params;

@ -20,8 +20,10 @@ decorator==4.4.2
Django==3.0.3 Django==3.0.3
django-apscheduler==0.3.0 django-apscheduler==0.3.0
django-qrcode==0.3 django-qrcode==0.3
django-ranged-response==0.2.0
django-redis==4.11.0 django-redis==4.11.0
django-rest-framework==0.1.0 django-rest-framework==0.1.0
django-simple-captcha==0.5.12
djangorestframework==3.11.0 djangorestframework==3.11.0
dnspython==1.16.0 dnspython==1.16.0
drf-extensions==0.6.0 drf-extensions==0.6.0

Loading…
Cancel
Save