diff --git a/go.mod b/go.mod index c1e959f8e..387a34520 100755 --- a/go.mod +++ b/go.mod @@ -22,14 +22,21 @@ require ( github.com/PuerkitoBio/goquery v1.5.0 github.com/RichardKnop/machinery v1.6.9 github.com/RoaringBitmap/roaring v0.4.23 // indirect + github.com/alibabacloud-go/darabonba-openapi v0.1.18 + github.com/alibabacloud-go/dysmsapi-20170525/v2 v2.0.9 + github.com/alibabacloud-go/tea v1.1.17 + github.com/alibabacloud-go/tea-utils v1.4.3 + github.com/alibabacloud-go/tea-xml v1.1.2 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect github.com/blevesearch/bleve v1.0.7 + github.com/clbanning/mxj/v2 v2.5.5 // indirect github.com/couchbase/gomemcached v0.0.0-20191004160342-7b5da2ec40b2 // indirect github.com/cznic/b v0.0.0-20181122101859-a26611c4d92d // indirect github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 // indirect github.com/cznic/strutil v0.0.0-20181122101858-275e90344537 // indirect github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc github.com/dgrijalva/jwt-go v3.2.0+incompatible + github.com/disintegration/imaging v1.6.2 github.com/dustin/go-humanize v1.0.0 github.com/editorconfig/editorconfig-core-go/v2 v2.1.1 github.com/elliotchance/orderedmap v1.4.0 @@ -56,6 +63,7 @@ require ( github.com/golang/protobuf v1.4.1 // indirect github.com/gomodule/redigo v2.0.0+incompatible github.com/google/go-github/v24 v24.0.1 + github.com/google/uuid v1.1.1 github.com/gorilla/context v1.1.1 github.com/gorilla/websocket v1.4.0 github.com/hashicorp/go-retryablehttp v0.6.6 // indirect @@ -112,9 +120,9 @@ require ( github.com/urfave/cli v1.22.1 github.com/xanzy/go-gitlab v0.31.0 github.com/yohcop/openid-go v1.0.0 - github.com/yuin/goldmark v1.1.27 + github.com/yuin/goldmark v1.1.30 github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60 - golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 + golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 golang.org/x/mod v0.3.0 // indirect golang.org/x/net v0.0.0-20200513185701-a91f0712d120 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d @@ -126,7 +134,7 @@ require ( gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/asn1-ber.v1 v1.0.0-20150924051756-4e86f4367175 // indirect gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df - gopkg.in/ini.v1 v1.52.0 + gopkg.in/ini.v1 v1.56.0 gopkg.in/ldap.v3 v3.0.2 gopkg.in/macaron.v1 v1.3.9 // indirect gopkg.in/testfixtures.v2 v2.5.0 diff --git a/go.sum b/go.sum index d9dc14b64..d55d7af48 100755 --- a/go.sum +++ b/go.sum @@ -78,6 +78,35 @@ github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBb github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.2/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= +github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 h1:iC9YFYKDGEy3n/FtqJnOkZsene9olVspKmkX5A2YBEo= +github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= +github.com/alibabacloud-go/darabonba-openapi v0.1.14/go.mod h1:w4CosR7O/kapCtEEMBm3JsQqWBU/CnZ2o0pHorsTWDI= +github.com/alibabacloud-go/darabonba-openapi v0.1.18 h1:3eUVmAr7WCJp7fgIvmCd9ZUyuwtJYbtUqJIed5eXCmk= +github.com/alibabacloud-go/darabonba-openapi v0.1.18/go.mod h1:PB4HffMhJVmAgNKNq3wYbTUlFvPgxJpTzd1F5pTuUsc= +github.com/alibabacloud-go/darabonba-string v1.0.0/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA= +github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68 h1:NqugFkGxx1TXSh/pBcU00Y6bljgDPaFdh5MUSeJ7e50= +github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY= +github.com/alibabacloud-go/dysmsapi-20170525/v2 v2.0.9 h1:z+OU7LbWtQitWJ8SAn55hEQkJPCsEPJc97TvGCZV+4s= +github.com/alibabacloud-go/dysmsapi-20170525/v2 v2.0.9/go.mod h1:AT91gCNJPsemf4lHLNgWTf/RsgmpdOprWvQ3FYvtwGk= +github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q= +github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE= +github.com/alibabacloud-go/openapi-util v0.0.10/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= +github.com/alibabacloud-go/openapi-util v0.0.11 h1:iYnqOPR5hyEEnNZmebGyRMkkEJRWUEjDiiaOHZ5aNhA= +github.com/alibabacloud-go/openapi-util v0.0.11/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= +github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg= +github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= +github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= +github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= +github.com/alibabacloud-go/tea v1.1.17 h1:05R5DnaJXe9sCNIe8KUgWHC/z6w/VZIwczgUwzRnul8= +github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= +github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= +github.com/alibabacloud-go/tea-utils v1.4.3 h1:8SzwmmRrOnQ09Hf5a9GyfJc0d7Sjv6fmsZoF4UDbFjo= +github.com/alibabacloud-go/tea-utils v1.4.3/go.mod h1:KNcT0oXlZZxOXINnZBs6YvgOd5aYp9U67G+E3R8fcQw= +github.com/alibabacloud-go/tea-xml v1.1.2 h1:oLxa7JUXm2EDFzMg+7oRsYc+kutgCVwm+bZlhhmvW5M= +github.com/alibabacloud-go/tea-xml v1.1.2/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8= +github.com/aliyun/credentials-go v1.1.2 h1:qU1vwGIBb3UJ8BwunHDRFtAhS6jnQLnde/yk0+Ih2GY= +github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw= github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o= github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= @@ -125,6 +154,8 @@ github.com/bradfitz/gomemcache v0.0.0-20190329173943-551aad21a668/go.mod h1:H0wQ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/chris-ramon/douceur v0.2.0 h1:IDMEdxlEUUBYBKE4z/mJnFyVXox+MjuEVDJNN27glkU= github.com/chris-ramon/douceur v0.2.0/go.mod h1:wDW5xjJdeoMm1mRt4sD4c/LbF/mWdEpRXQKjTR8nIBE= +github.com/clbanning/mxj/v2 v2.5.5 h1:oT81vUeEiQQ/DcHbzSytRngP6Ky9O+L+0Bw0zSJag9E= +github.com/clbanning/mxj/v2 v2.5.5/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/corbym/gocrest v1.0.3 h1:gwEdq6RkTmq+09CTuM29DfKOCtZ7G7bcyxs3IZ6EVdU= github.com/corbym/gocrest v1.0.3/go.mod h1:maVFL5lbdS2PgfOQgGRWDYTeunSWQeiEgoNdTABShCs= @@ -170,6 +201,8 @@ github.com/denisenkom/go-mssqldb v0.0.0-20200428022330-06a60b6afbbc/go.mod h1:xb github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= +github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= @@ -369,6 +402,7 @@ github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORR github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99 h1:twflg0XRTjwKpxb/jFExr4HGq6on2dEOmnL6FV+fgPw= github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= @@ -432,6 +466,8 @@ github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCV github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= @@ -673,11 +709,13 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1 github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w= github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= +github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8= github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= @@ -707,6 +745,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stvp/tempredis v0.0.0-20181119212430-b82af8480203 h1:QVqDTf3h2WHt08YuiTGPZLls0Wq99X9bWd0Q5ZSBesM= @@ -722,6 +761,8 @@ github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhV github.com/tinylib/msgp v1.1.0/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= github.com/tinylib/msgp v1.1.2 h1:gWmO7n0Ys2RBEb7GPYB9Ujq8Mk5p2U08lRnmMcGy6BQ= github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE= +github.com/tjfoc/gmsm v1.3.2 h1:7JVkAn5bvUJ7HtU08iW6UiD+UTmJTIToHCfeFzkcCxM= +github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/toqueteos/trie v1.0.0 h1:8i6pXxNUXNRAqP246iibb7w/pSFquNTQ+uNfriG7vlk= github.com/toqueteos/trie v1.0.0/go.mod h1:Ywk48QhEqhU1+DwhMkJ2x7eeGxDHiGkAdc9+0DYcbsM= @@ -761,6 +802,8 @@ github.com/yuin/goldmark v1.1.7/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27 h1:nqDD4MMMQA0lmWq03Z2/myGPYLQoXtmi0rGVs95ntbo= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.30 h1:j4d4Lw3zqZelDhBksEo3BnWg9xhXRQGJPPSL6OApZjI= +github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60 h1:gZucqLjL1eDzVWrXj4uiWeMbAopJlBR2mKQAsTGdPwo= github.com/yuin/goldmark-meta v0.0.0-20191126180153-f0638e958b60/go.mod h1:i9VhcIHN2PxXMbQrKqXNueok6QNONoPjNMoj9MygVL0= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= @@ -800,14 +843,19 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190907121410-71b5226ff739/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190927123631-a832865fa7ad/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79 h1:IaQbIIB2X/Mp/DKctl6ROxz1KyMlKp4uyvL6+kQ7C88= golang.org/x/crypto v0.0.0-20200429183012-4b2356b1ed79/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37 h1:cg5LA/zNPRzIXIWSCxQW10Rvpy94aQh3LT/ShoCpkHw= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a h1:gHevYm0pO4QUbwy8Dmdr01R5r1BuKtfYqRqF0h/Cbh0= golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -848,6 +896,7 @@ golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120 h1:EZ3cVSzKOlJxAd8e8YAJ7no8nNypTxexh/YE/xW3ZEY= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/oauth2 v0.0.0-20180620175406-ef147856a6dd/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -868,6 +917,8 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180824143301-4910a1d54f87/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -933,6 +984,7 @@ golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200225230052-807dcd883420/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200325010219-a49f79bcc224/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515220128-d3bf790afa53 h1:vmsb6v0zUdmUlXfwKaYrHPPRCV0lHq/IwNIf0ASGjyQ= golang.org/x/tools v0.0.0-20200515220128-d3bf790afa53/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1012,6 +1064,8 @@ gopkg.in/ini.v1 v1.44.2/go.mod h1:M3Cogqpuv0QCi3ExAY5V4uOt4qb/R3xZubo9m8lK5wg= gopkg.in/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.52.0 h1:j+Lt/M1oPPejkniCg1TkWE2J3Eh1oZTsHSXzMTzUXn4= gopkg.in/ini.v1 v1.52.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.56.0 h1:DPMeDvGTM54DXbPkVIZsp19fp/I2K7zwA/itHYHKo8Y= +gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ldap.v3 v3.0.2 h1:R6RBtabK6e1GO0eQKtkyOFbAHO73QesLzI2w2DZ6b9w= gopkg.in/ldap.v3 v3.0.2/go.mod h1:oxD7NyBuxchC+SgJDE1Q5Od05eGt29SDQVBmV+HYbzw= gopkg.in/macaron.v1 v1.3.9 h1:Dw+DDRYdXgQyEsPlfAfKz+UA5qVUrH3KPD7JhmZ9MFc= diff --git a/models/custom_migrations.go b/models/custom_migrations.go index 65b53f0f4..041c4fb93 100755 --- a/models/custom_migrations.go +++ b/models/custom_migrations.go @@ -15,10 +15,15 @@ type CustomMigrationStatic struct { Migrate func(*xorm.Engine, *xorm.Engine) error } -var customMigrations []CustomMigration +var customMigrations = []CustomMigration{ + //手机号功能可以不启用,不启用时手机号为空串,为了避免启用时唯一性约束报错,定制唯一性约束(对null和空字符串不做唯一性检查) + {"set phone number unique index", setPhoneNumberUniqueIndex}, +} var customMigrationsStatic []CustomMigrationStatic + + func MigrateCustom(x *xorm.Engine) { for _, m := range customMigrations { @@ -35,13 +40,20 @@ func MigrateCustomStatic(x *xorm.Engine, static *xorm.Engine) { for _, m := range customMigrationsStatic { log.Info("Migration: %s", m.Description) if err := m.Migrate(x, static); err != nil { - log.Error("Migration: %v", err) - } } } +func setPhoneNumberUniqueIndex(x *xorm.Engine) error { + + query := "CREATE UNIQUE INDEX IF NOT EXISTS \"UQE_user_phone_number\" ON \"user\" (phone_number) WHERE phone_number IS NOT NULL and phone_number !='';" + + _, err := x.Exec(query) + return err +} + + func syncTopicStruct(x *xorm.Engine) error { query := "ALTER TABLE topic ALTER COLUMN name TYPE varchar(105);" diff --git a/models/login_source.go b/models/login_source.go index ca2aa5a4d..5641e5c59 100755 --- a/models/login_source.go +++ b/models/login_source.go @@ -766,6 +766,14 @@ func UserSignIn(username, password string) (*User, error) { if err != nil { return nil, err } + //email和用户名方式没找到,用手机号查找 + if !hasUser { + user = &User{PhoneNumber: strings.TrimSpace(username)} + hasUser, err = x.Get(user) + if err != nil { + return nil, err + } + } if hasUser { switch user.LoginType { diff --git a/models/user.go b/models/user.go index 73537556a..e08832b8e 100755 --- a/models/user.go +++ b/models/user.go @@ -184,6 +184,8 @@ type User struct { //Wechat WechatOpenId string `xorm:"INDEX"` WechatBindUnix timeutil.TimeStamp + //Mobile phone + PhoneNumber string `xorm:"VARCHAR(255)"` } type UserShow struct { @@ -1446,6 +1448,31 @@ func getUserByName(e Engine, name string) (*User, error) { return u, nil } +func GetUserByPhoneNumber(phoneNumber string) (*User, error) { + return getUserByPhoneNumber(x, phoneNumber) +} + +func getUserByPhoneNumber(e Engine, phoneNumber string) (*User, error) { + u := &User{PhoneNumber: phoneNumber} + has, err := e.Get(u) + if err != nil { + return nil, err + } else if !has { + return nil, ErrUserNotExist{0, "", 0} + } + return u, nil +} + +func IsUserByPhoneNumberExist(phoneNumber string) (bool, error) { + return isUserByPhoneNumberExist(x, phoneNumber) + +} + +func isUserByPhoneNumberExist(e Engine, phoneNumber string) (bool, error) { + return e.Where("phone_number = ?", phoneNumber).Exist(&User{}) + +} + // GetUserEmailsByNames returns a list of e-mails corresponds to names of users // that have their email notifications set to enabled or onmention. func GetUserEmailsByNames(names []string) []string { @@ -1614,6 +1641,26 @@ func GetUserByEmail(email string) (*User, error) { return GetUserByEmailContext(DefaultDBContext(), email) } +func GetUserByMainEmail(email string) (*User, error) { + if len(email) == 0 { + return nil, ErrUserNotExist{0, email, 0} + } + + email = strings.ToLower(email) + // First try to find the user by primary email + user := &User{Email: email} + has, err := DefaultDBContext().e.Get(user) + if err != nil { + return nil, err + } + if has { + return user, nil + } else { + return nil, ErrUserNotExist{0, email, 0} + } + +} + // GetUserByEmailContext returns the user object by given e-mail if exists with db context func GetUserByEmailContext(ctx DBContext, email string) (*User, error) { if len(email) == 0 { diff --git a/modules/auth/user_form.go b/modules/auth/user_form.go index 86771b9f8..130586a5a 100755 --- a/modules/auth/user_form.go +++ b/modules/auth/user_form.go @@ -81,8 +81,11 @@ type RegisterForm struct { UserName string `binding:"Required;AlphaDashDot;MaxSize(40)"` Email string `binding:"Required;Email;MaxSize(254)"` Password string `binding:"MaxSize(255)"` + PhoneNumber string `binding:"MaxSize(20)"` + VerifyCode string `binding:"MaxSize(10)"` Retype string GRecaptchaResponse string `form:"g-recaptcha-response"` + Agree bool } // Validate valideates the fields @@ -105,7 +108,7 @@ func (f RegisterForm) IsEmailDomainWhitelisted() bool { } domain := strings.ToLower(f.Email[n+1:]) - + //support edu.cn if strings.HasSuffix(domain, "edu.cn") { return true @@ -209,6 +212,8 @@ type UpdateProfileForm struct { Location string `binding:"MaxSize(50)"` Language string `binding:"Size(5)"` Description string `binding:"MaxSize(255)"` + PhoneNumber string `binding:"MaxSize(20)"` + VerifyCode string `binding:"MaxSize(10)"` } // Validate validates the fields @@ -364,3 +369,43 @@ type U2FDeleteForm struct { func (f *U2FDeleteForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { return validate(errs, ctx.Data, f, ctx.Locale) } + +type PhoneNumberForm struct { + PhoneNumber string `binding:"Required;MaxSize(20)"` + Mode int `binding:"Required"` + SlideID string `binding:"Required;MaxSize(100)"` +} + +func (f *PhoneNumberForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +type PhoneNumberCodeForm struct { + PhoneNumber string `binding:"Required;MaxSize(20)"` + VerifyCode string `binding:"Required;MaxSize(10)"` + Remember bool +} + +func (f *PhoneNumberCodeForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +type ResetPassWordByPhoneForm struct { + PhoneNumber string `binding:"Required;MaxSize(20)"` + VerifyCode string `binding:"Required;MaxSize(10)"` + Password string `binding:"MaxSize(255)"` + Remember bool +} + +func (f *ResetPassWordByPhoneForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} + +type SlideImageForm struct { + SlideID string `binding:"Required"` + X int `binding:"Required"` +} + +func (f *SlideImageForm) Validate(ctx *macaron.Context, errs binding.Errors) binding.Errors { + return validate(errs, ctx.Data, f, ctx.Locale) +} diff --git a/modules/context/auth.go b/modules/context/auth.go index 287823dea..efde3bc2e 100755 --- a/modules/context/auth.go +++ b/modules/context/auth.go @@ -53,6 +53,10 @@ func Toggle(options *ToggleOptions) macaron.Handler { ctx.Data["Title"] = ctx.Tr("auth.prohibit_login") ctx.HTML(200, "user/auth/prohibit_login") return + } else if setting.PhoneService.Enabled && ctx.User.IsActive && ctx.User.PhoneNumber == "" && ctx.Req.URL.Path != "/bindPhone" { + ctx.Data["Title"] = ctx.Tr("phone.bind_phone") + ctx.HTML(200, "user/auth/bind_phone") + return } if ctx.User.MustChangePassword { diff --git a/modules/phone/phone.go b/modules/phone/phone.go new file mode 100644 index 000000000..9349a75ec --- /dev/null +++ b/modules/phone/phone.go @@ -0,0 +1,61 @@ +package phone + +import ( + "math" + "math/rand" + "regexp" + "strconv" + "time" + + "code.gitea.io/gitea/modules/setting" + + openapi "github.com/alibabacloud-go/darabonba-openapi/client" + dysmsapi20170525 "github.com/alibabacloud-go/dysmsapi-20170525/v2/client" + util "github.com/alibabacloud-go/tea-utils/service" + "github.com/alibabacloud-go/tea/tea" +) + +func IsValidPhoneNumber(phoneNumber string) bool { + pattern := "^1[3-9]\\d{9}$" + match, _ := regexp.MatchString(pattern, phoneNumber) + return match +} + +func GenerateVerifyCode(n int) string { + min := int(math.Pow10(n - 1)) + max := int(math.Pow10(n)) + rand.Seed(time.Now().UnixNano()) + return strconv.Itoa(rand.Intn(max-min) + min) + +} + +func createClient(accessKeyId *string, accessKeySecret *string) (_result *dysmsapi20170525.Client, _err error) { + config := &openapi.Config{ + // 您的AccessKey ID + AccessKeyId: accessKeyId, + // 您的AccessKey Secret + AccessKeySecret: accessKeySecret, + } + // 访问的域名 + config.Endpoint = tea.String("dysmsapi.aliyuncs.com") + _result = &dysmsapi20170525.Client{} + _result, _err = dysmsapi20170525.NewClient(config) + return _result, _err +} + +func SendVerifyCode(phoneNumber string, verifyCode string) error { + client, _err := createClient(&setting.PhoneService.AccessKeyId, &setting.PhoneService.AccessKeySecret) + if _err != nil { + return _err + } + + sendSmsRequest := &dysmsapi20170525.SendSmsRequest{ + SignName: tea.String(setting.PhoneService.SignName), + TemplateCode: tea.String(setting.PhoneService.TemplateCode), + PhoneNumbers: tea.String(phoneNumber), + TemplateParam: tea.String("{\"code\":\"" + verifyCode + "\"}"), + } + runtime := &util.RuntimeOptions{} + _, _err = client.SendSmsWithOptions(sendSmsRequest, runtime) + return _err +} diff --git a/modules/public/dynamic.go b/modules/public/dynamic.go index 07f83e1f6..4f10895a1 100644 --- a/modules/public/dynamic.go +++ b/modules/public/dynamic.go @@ -7,10 +7,53 @@ package public import ( + "fmt" + "io/ioutil" + "os" + "path" + + "code.gitea.io/gitea/modules/setting" "gitea.com/macaron/macaron" + "github.com/unknwon/com" ) // Static implements the macaron static handler for serving assets. func Static(opts *Options) macaron.Handler { return opts.staticHandler(opts.Directory) } + +func Dir(name string) ([]string, error) { + + var ( + result []string + ) + + staticDir := path.Join(setting.StaticRootPath, "public", name) + + if com.IsDir(staticDir) { + files, err := com.StatDir(staticDir, true) + + if err != nil { + return []string{}, fmt.Errorf("Failed to read img directory. %v", err) + } + + result = append(result, files...) + } + + return result, nil +} + +func Asset(name string) ([]byte, error) { + + staticPath := path.Join(setting.StaticRootPath, "public", name) + + if com.IsFile(staticPath) { + f, err := os.Open(staticPath) + defer f.Close() + + if err == nil { + return ioutil.ReadAll(f) + } + } + return nil, fmt.Errorf("Asset file does not exist: %s", name) +} diff --git a/modules/public/static.go b/modules/public/static.go index 76050632c..6eac8a751 100644 --- a/modules/public/static.go +++ b/modules/public/static.go @@ -7,6 +7,7 @@ package public import ( + "fmt" "io/ioutil" "gitea.com/macaron/macaron" @@ -20,6 +21,16 @@ func Static(opts *Options) macaron.Handler { return opts.staticHandler("") } +func Dir(name string) ([]string, error) { + + files, err := AssetDir(name) + if err != nil { + return []string{}, fmt.Errorf("Failed to read embedded directory. %v", err) + } + + return files, nil +} + func Asset(name string) ([]byte, error) { f, err := Assets.Open("/" + name) if err != nil { @@ -29,6 +40,24 @@ func Asset(name string) ([]byte, error) { return ioutil.ReadAll(f) } +func AssetDir(dirName string) ([]string, error) { + d, err := Assets.Open(dirName) + if err != nil { + return nil, err + } + defer d.Close() + + files, err := d.Readdir(-1) + if err != nil { + return nil, err + } + var results = make([]string, 0, len(files)) + for _, file := range files { + results = append(results, file.Name()) + } + return results, nil +} + func AssetNames() []string { realFS := Assets.(vfsgen۰FS) var results = make([]string, 0, len(realFS)) diff --git a/modules/redis/redis_client/client.go b/modules/redis/redis_client/client.go index e795234df..6f650bf5d 100644 --- a/modules/redis/redis_client/client.go +++ b/modules/redis/redis_client/client.go @@ -41,6 +41,66 @@ func Setnx(key, value string, timeout time.Duration) (bool, error) { } +func SETNX(conn redis.Conn, key, value string, seconds int) (bool, error) { + reply, err := conn.Do("SET", key, value, "NX", "EX", seconds) + return redis.Bool(reply, err) + +} + +func SET(conn redis.Conn, key, value string, seconds int) (bool, error) { + reply, err := conn.Do("SETEX", key, seconds, value) + return redis.Bool(reply, err) + +} + +func HSETNX(conn redis.Conn, key, subKey string, value interface{}) error { + _, err := conn.Do("HSETNX", key, subKey, value) + return err + +} + +func HGET(conn redis.Conn, key, subKey string) (interface{}, error) { + return conn.Do("HGET", key, subKey) +} +func EXISTS(conn redis.Conn, key string) (bool, error) { + + reply, err := conn.Do("EXISTS", key) + return redis.Bool(reply, err) + +} + +func HEXISTS(conn redis.Conn, key string, subKey string) (bool, error) { + + reply, err := conn.Do("HEXISTS", key, subKey) + return redis.Bool(reply, err) + +} + +func EXPIRE(conn redis.Conn, key string, seconds int) error { + _, err := conn.Do("EXPIRE", key, seconds) + return err + +} + +func HINCRBY(conn redis.Conn, key, subKey string, value int) error { + _, err := conn.Do("HINCRBY", key, subKey, value) + return err +} +func GET(conn redis.Conn, key string) (interface{}, error) { + return conn.Do("GET", key) +} + +func Ttl(conn redis.Conn, key string) (int, error) { + + reply, err := conn.Do("TTL", key) + if err != nil { + return 0, err + } + n, _ := strconv.Atoi(fmt.Sprint(reply)) + return n, nil + +} + func Get(key string) (string, error) { redisClient := labelmsg.Get() defer redisClient.Close() diff --git a/modules/setting/phone.go b/modules/setting/phone.go new file mode 100644 index 000000000..16b4ebf19 --- /dev/null +++ b/modules/setting/phone.go @@ -0,0 +1,47 @@ +package setting + +import ( + "code.gitea.io/gitea/modules/log" +) + +type Phone struct { + Enabled bool + VerifyCodeLength int + AccessKeyId string + AccessKeySecret string + SignName string + TemplateCode string + CodeTimeout int + RetryInterval int + MaxRetryTimes int +} + +var ( + // Phone verify info + PhoneService *Phone +) + +func newPhoneService() { + sec := Cfg.Section("phone") + // Check phone setting. + if !sec.Key("ENABLED").MustBool() { + PhoneService = &Phone{ + Enabled: sec.Key("ENABLED").MustBool(), + } + return + } + + PhoneService = &Phone{ + Enabled: sec.Key("ENABLED").MustBool(), + VerifyCodeLength: sec.Key("VERIFY_CODE_LEN").MustInt(6), + AccessKeyId: sec.Key("AccessKeyId").String(), + AccessKeySecret: sec.Key("AccessKeySecret").String(), + SignName: sec.Key("SignName").String(), + TemplateCode: sec.Key("TemplateCode").String(), + CodeTimeout: sec.Key("CODE_TIMEOUT").MustInt(60 * 5), + RetryInterval: sec.Key("RETRY_INTERVAL").MustInt(60 * 2), + MaxRetryTimes: sec.Key("MAX_RETRY").MustInt(5), + } + + log.Info("Phone Service Enabled") +} diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 917681595..abfd9e263 100755 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -1568,4 +1568,5 @@ func NewServices() { newIndexerService() newTaskService() NewQueueService() + newPhoneService() } diff --git a/modules/setting/slideimage.go b/modules/setting/slideimage.go new file mode 100644 index 000000000..c68beb185 --- /dev/null +++ b/modules/setting/slideimage.go @@ -0,0 +1,12 @@ +package setting + +import ( + "image" +) + +var ( + // the original images for generate slide image + SlideImagesBg []*image.Image + SlideMaskImage *image.Image + SlideImagesCount int +) diff --git a/modules/slideimage/slideimage.go b/modules/slideimage/slideimage.go new file mode 100644 index 000000000..748cc2d6c --- /dev/null +++ b/modules/slideimage/slideimage.go @@ -0,0 +1,308 @@ +package slideimage + +import ( + "bytes" + "image" + "image/png" + "math" + "math/rand" + "path" + "strconv" + "strings" + "time" + + "code.gitea.io/gitea/modules/labelmsg" + "github.com/gomodule/redigo/redis" + + "code.gitea.io/gitea/modules/public" + + "code.gitea.io/gitea/modules/log" + + "code.gitea.io/gitea/modules/setting" + + "code.gitea.io/gitea/modules/redis/redis_client" + + "gitea.com/macaron/macaron" + "github.com/disintegration/imaging" + "github.com/google/uuid" +) + +type SlideImage struct { + SubURL string + URLPrefix string + SampleImages int + StdWidth int + StdHeight int + MaskSize int + ImageY int + ImageXStart int + MinImageX int + Expiration int + Tolerance int + CachePrefix string + CacheManualPrefix string +} + +type Options struct { + // Suburl path. Default is empty. + SubURL string + // URL prefix of getting captcha pictures. Default is "/slideimage/". + URLPrefix string + //Default is 4 + SampleImages int + //Image width default 391 + StdWidth int + //Image Height default 196 + StdHeight int + // default 51 + MaskSize int + // default 125 + ImageY int + //default 0 + ImageXStart int + //容忍的误差 default 2px + Tolerance int + // default 150 + MinImageX int + // default 600 seconds + Expiration int + //default slide: + CachePrefix string + //default mslide: 验证通过,在缓存中记录已进行过人工操作,然后在发送验证码之前再进行校验是否进行了人工操作。 + CacheManualPrefix string +} + +func NewSlideImage(opt Options) *SlideImage { + return &SlideImage{ + SubURL: opt.SubURL, + URLPrefix: opt.URLPrefix, + SampleImages: opt.SampleImages, + StdWidth: opt.StdWidth, + StdHeight: opt.StdHeight, + MaskSize: opt.MaskSize, + ImageY: opt.ImageY, + ImageXStart: opt.ImageXStart, + Tolerance: opt.Tolerance, + MinImageX: opt.MinImageX, + Expiration: opt.Expiration, + CachePrefix: opt.CachePrefix, + } +} + +func prepareOptions(options []Options) Options { + var opt Options + if len(options) > 0 { + opt = options[0] + } + + opt.SubURL = strings.TrimSuffix(opt.SubURL, "/") + + // Defaults. + if len(opt.URLPrefix) == 0 { + opt.URLPrefix = "/slideimage/" + } else if opt.URLPrefix[len(opt.URLPrefix)-1] != '/' { + opt.URLPrefix += "/" + } + if opt.SampleImages == 0 { + opt.SampleImages = 4 + } + if opt.StdWidth == 0 { + opt.StdWidth = 391 + } + if opt.StdHeight == 0 { + opt.StdHeight = 196 + } + if opt.MaskSize == 0 { + opt.MaskSize = 51 + } + if opt.ImageY == 0 { + opt.ImageY = 75 + } + if opt.ImageXStart == 0 { + opt.ImageXStart = 2 + } + + if opt.Tolerance == 0 { + opt.Tolerance = 2 + } + + if opt.MinImageX == 0 { + opt.MinImageX = 150 + } + if opt.Expiration == 0 { + opt.Expiration = 600 + } + + if len(opt.CachePrefix) == 0 { + opt.CachePrefix = "slide:" + } + if len(opt.CacheManualPrefix) == 0 { + opt.CacheManualPrefix = "mslide:" + } + + return opt +} + +func (s *SlideImage) key(id string) string { + return s.CachePrefix + id +} +func (s *SlideImage) mkey(id string) string { + return s.CacheManualPrefix + id +} + +func (s *SlideImage) VerifyManual(id string) (bool, error) { + redisConn := labelmsg.Get() + defer redisConn.Close() + return redis_client.EXISTS(redisConn, s.mkey(id)) +} + +func (s *SlideImage) Verify(id string, x int) bool { + redisConn := labelmsg.Get() + defer redisConn.Close() + + v, err := redis_client.GET(redisConn, s.key(id)) + v1, err := redis.String(v, err) + if err != nil { + log.Warn("redis err", err) + return false + } + if v1 == "" { + return false + } + values := strings.Split(v1, "-") + imageRandX, _ := strconv.Atoi(values[1]) + if int(math.Abs(float64(imageRandX-x))) <= s.Tolerance { + redis_client.SETNX(redisConn, s.mkey(id), "1", s.Expiration) + return true + } + return false + +} + +func (s *SlideImage) CreateCode() (string, int, int) { + nums := rand.Intn(s.SampleImages) + imageId := uuid.New().String() + + //获取随机x坐标 + imageRandX := rand.Intn(s.StdWidth - s.MaskSize - 3) + if imageRandX < s.MinImageX { + imageRandX += s.MinImageX + } + redis_client.Setex(s.key(imageId), strconv.Itoa(nums)+"-"+strconv.Itoa(imageRandX), time.Second*time.Duration(s.Expiration)) + return imageId, nums, imageRandX +} + +func SlideImager(options ...Options) macaron.Handler { + return func(ctx *macaron.Context) { + slideImage := NewSlideImage(prepareOptions(options)) + + if strings.HasPrefix(ctx.Req.URL.Path, slideImage.URLPrefix) { + id := path.Base(ctx.Req.URL.Path) + if i := strings.Index(id, "."); i > -1 { + id = id[:i] + } + isScreenshot := strings.HasSuffix(id, "screenshot") + if isScreenshot { + id = strings.TrimSuffix(id, "screenshot") + } + + key := slideImage.key(id) + v, err := redis_client.Get(key) + + if err != nil || v == "" { + ctx.Status(404) + ctx.Write([]byte("not found")) + //png.Encode(ctx.Resp, *setting.SlideImagesBg[0]) + return + } + + values := strings.Split(v, "-") + + imageIndex, _ := strconv.Atoi(values[0]) + imageRandX, _ := strconv.Atoi(values[1]) + imageBg := setting.SlideImagesBg[imageIndex] + + maxPotion := image.Point{ + X: imageRandX + slideImage.MaskSize, + Y: slideImage.ImageY + slideImage.MaskSize, + } + minPotion := image.Point{ + X: imageRandX, + Y: slideImage.ImageY, + } + subimg := image.Rectangle{ + Max: maxPotion, + Min: minPotion, + } + + if isScreenshot { + data := imaging.Crop(*imageBg, subimg) + png.Encode(ctx.Resp, data) + + } else { + data := imaging.Overlay(*imageBg, *setting.SlideMaskImage, minPotion, 1.0) + png.Encode(ctx.Resp, data) + } + ctx.Status(200) + return + + } + ctx.Data["SlideImageInfo"] = slideImage + ctx.Data["EnablePhone"] = setting.PhoneService.Enabled + ctx.Map(slideImage) + + } +} + +func InitSlideImage() { + if setting.PhoneService.Enabled { + + filenames, err := public.Dir(path.Join("img", "slide", "bg")) + if err != nil { + panic("Slide Image Service init failed") + } + maskFileName, err := public.Dir(path.Join("img", "slide", "mask")) + if err != nil { + panic("Slide Image Service init failed") + } + + for _, filename := range filenames { + if strings.HasSuffix(filename, ".png") { + content, err := public.Asset(path.Join("img", "slide", "bg", filename)) + + if err != nil { + log.Warn("can not open "+filename, err) + continue + } + + m, err := png.Decode(bytes.NewReader(content)) + + if err != nil { + log.Warn("can not decode "+filename, err) + continue + } + setting.SlideImagesBg = append(setting.SlideImagesBg, &m) + } + + } + setting.SlideImagesCount = len(setting.SlideImagesBg) + if setting.SlideImagesCount == 0 { + panic("Slide Image Service init failed") + } + + maskContent, err := public.Asset(path.Join("img", "slide", "mask", maskFileName[0])) + + if err != nil { + panic("Slide Image Service init failed") + } + + MaskImage, err := png.Decode(bytes.NewReader(maskContent)) + if err != nil { + panic("Slide Image Service init failed") + } + setting.SlideMaskImage = &MaskImage + + log.Info("Slide Image Service Enabled") + + } +} diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index 7d5571e40..ce19e87d2 100755 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -33,6 +33,8 @@ captcha = CAPTCHA twofa = Two-Factor Authentication twofa_scratch = Two-Factor Scratch Code passcode = Passcode +use_and_privacy_agree = I have read and agree to the use agreement and privacy agreement +sign_up_agree_tips = should agree to the use agreement and privacy agreement! u2f_insert_key = Insert your security key u2f_sign_in = Press the button on your security key. If your security key has no button, re-insert it. @@ -198,6 +200,7 @@ no_reply_address_helper = Domain name for users with a hidden email address. For [home] uname_holder = Username or Email Address +login_uname_holder=Username/Email/Phone number uname_holder_cloud_brain = cloudbrain username password_holder = Password switch_dashboard_context = Switch Dashboard Context @@ -336,11 +339,16 @@ resent_limit_prompt = You have already requested an activation email recently. P has_unconfirmed_mail = Hi %s, you have an unconfirmed email address (%s). If you haven't received a confirmation email or need to resend a new one, please click on the button below. resend_mail = Click here to resend your activation email email_not_associate = The email address is not associated with any account. +email_not_main=The email address is wrong, please input your primary email address. +email_not_right=The email address is not associated with any account, please input the right email address. send_reset_mail = Send Account Recovery Email +please_enter_main_email=Please enter your primary email +please_enter_main_email_tips=Tips: Only the primary email can receive the recovery email. reset_password = Account Recovery invalid_code = Your confirmation code is invalid or has expired. reset_password_helper = Recover Account reset_password_wrong_user = You are signed in as %s, but the account recovery link is for %s +reset_password_wrong_user_phone=You are signed in, but the phone number is used by other user. password_too_short = Password length cannot be less than %d characters. non_local_account = Non-local users can not update their password through the openi web interface. verify = Verify @@ -375,6 +383,37 @@ authorization_failed = Authorization failed authorization_failed_desc = The authorization failed because we detected an invalid request. Please contact the maintainer of the app you've tried to authorize. disable_forgot_password_mail = Account recovery is disabled. Please contact your site administrator. sspi_auth_failed = SSPI authentication failed +[phone] +format_err=The format of phone number is wrong. +query_err=Fail to query phone number, please try again later. +already_register=The phone number is already used. +not_register=The phone number is wrong. +not_modify=The phone number is not updated. +max_times=One phone number can not send verification code more than %s times a day. +too_fast=Send too frequently, please try again later. +manual_first=Please slide to finish the jigsaw first. +verify_code_fail=Please input right verification code. +bind_phone=Please Bind Your Phone. +bind_phone_fail=Fail to bind phone number, please try again later. +phone_number=Phone number +drag_the_slider_to_fill_the_puzzle=Drag the slider to the right to fill the puzzle +mobile_phone_verification_code=Phone verification code +please_enter_SMS_verification_code=Please enter SMS verification code +get_verification_code=Get verification code +new_login_password=New login password +please_enter_new_password=Please enter new password +second_resend=S resend +please_bind_your_mobile_number=Please Bind Your Phone Number +submit=Submit +please_enter_the_correct_mobile_number=Please enter the correct phone number +please_enter_the_correct_mobile_phone_verification_code=Please enter the correct phone verification code +email_retrieve_password=Email retrieve password +mobile_number_retrieve_password=Phone number retrieve password +mobile_login=Mobile login +account_password_login=Account password login +cloud_brain_user_login=Cloud brain user login +modify_phone_number=Modify phone number + [mail] activate_account = Please activate your account @@ -2503,6 +2542,7 @@ users.new_account = Create User Account users.name = Username users.full_name = Full Name users.activated = Activated +users.bind_phone = Bind Phone users.admin = Admin users.restricted = Restricted users.repos = Repos diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index e5b6b5e6a..793f6feed 100755 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -33,6 +33,8 @@ captcha=验证码 twofa=两步验证 twofa_scratch=两步验证口令 passcode=验证码 +use_and_privacy_agree = 我已阅读并同意 使用协议隐私协议 +sign_up_agree_tips = 注册时需同意使用协议和隐私协议 u2f_insert_key=插入安全密钥 u2f_sign_in=按下安全密钥上的按钮。如果安全密钥没有按钮,请重新插入。 @@ -199,6 +201,7 @@ no_reply_address_helper=具有隐藏电子邮件地址的用户的域名。例 [home] uname_holder=登录名或电子邮箱地址 +login_uname_holder=用户名/邮箱/手机号 uname_holder_cloud_brain=云脑登录名 password_holder=密码 switch_dashboard_context=切换控制面板用户 @@ -340,11 +343,16 @@ resent_limit_prompt=您请求发送激活邮件过于频繁,请等待 3 分钟 has_unconfirmed_mail=%s 您好,系统检测到您有一封发送至 %s 但未被确认的邮件。如果您未收到激活邮件,或需要重新发送,请单击下方的按钮。 resend_mail=单击此处重新发送确认邮件 email_not_associate=您输入的邮箱地址未被关联到任何帐号! -send_reset_mail=发送账户恢复邮件 +email_not_main=电子邮箱地址不正确,请输入您设置的主要邮箱地址。 +email_not_right=您输入了不存在的邮箱地址,请输入正确的邮箱地址。 +send_reset_mail=发送密码找回邮件 +please_enter_main_email=请输入接收通知提醒的主要邮箱地址 +please_enter_main_email_tips=说明:如果您设置了多个邮箱地址,只有主要邮箱可以收到密码找回邮件,其他邮箱无法收到。 reset_password=账户恢复 invalid_code=此确认密钥无效或已过期。 reset_password_helper=恢复账户 reset_password_wrong_user=您已作为 %s 登录,无法使用链接恢复 %s 的账户。 +reset_password_wrong_user_phone=您已登录,不能用别的账号的手机恢复。 password_too_short=密码长度不能少于 %d 位。 non_local_account=非本地帐户不能通过 openi 的 web 界面更改密码。 verify=验证 @@ -379,6 +387,37 @@ authorization_failed=授权失败 authorization_failed_desc=授权失败,这是一个无效的请求。请联系尝试授权应用的管理员。 disable_forgot_password_mail = Account recovery is disabled. Please contact your site administrator. sspi_auth_failed=SSPI 认证失败 +[phone] +format_err=手机号格式错误。 +query_err=查询手机号失败,请稍后再试。 +already_register=手机号已被使用。 +not_register=手机号输入错误。 +not_modify=手机号未修改。 +max_times=一个手机号每天发送验证码次数不能超过%s次。 +too_fast=验证码发送太频繁,请稍后再试。 +manual_first=请先拖动滑块填充拼图。 +verify_code_fail=请输入正确的短信验证码。 +bind_phone=请绑定手机号。 +bind_phone_fail=绑定手机号失败,请稍后再试。 +phone_number=手机号码 +drag_the_slider_to_fill_the_puzzle=向右拖动滑块填充拼图 +mobile_phone_verification_code=手机验证码 +please_enter_SMS_verification_code=请输入短信验证码 +get_verification_code=获取验证码 +new_login_password=新的登录密码 +please_enter_new_password=请输入新的密码 +second_resend=S后重发 +please_bind_your_mobile_number=请绑定手机号 +submit=提交 +please_enter_the_correct_mobile_number=请输入正确的手机号 +please_enter_the_correct_mobile_phone_verification_code=请输入正确格式的手机验证码 +email_retrieve_password=邮箱找回密码 +mobile_number_retrieve_password=手机号找回密码 +mobile_login=手机登录 +account_password_login=账号密码登录 +cloud_brain_user_login=云脑1用户登录 +modify_phone_number=修改手机号 + [mail] activate_account=请激活您的帐户 @@ -2517,6 +2556,7 @@ users.new_account=创建新帐户 users.name=用户名 users.full_name=全名 users.activated=已激活 +users.bind_phone=手机验证 users.admin=管理员 users.restricted=受限 users.repos=项目数 diff --git a/public/img/slide/bg/1.png b/public/img/slide/bg/1.png new file mode 100644 index 000000000..d76aa3725 Binary files /dev/null and b/public/img/slide/bg/1.png differ diff --git a/public/img/slide/bg/2.png b/public/img/slide/bg/2.png new file mode 100644 index 000000000..73536b138 Binary files /dev/null and b/public/img/slide/bg/2.png differ diff --git a/public/img/slide/bg/3.png b/public/img/slide/bg/3.png new file mode 100644 index 000000000..530e5b5c0 Binary files /dev/null and b/public/img/slide/bg/3.png differ diff --git a/public/img/slide/bg/4.png b/public/img/slide/bg/4.png new file mode 100644 index 000000000..c9b0127f0 Binary files /dev/null and b/public/img/slide/bg/4.png differ diff --git a/public/img/slide/mask/mask.png b/public/img/slide/mask/mask.png new file mode 100644 index 000000000..562f75b05 Binary files /dev/null and b/public/img/slide/mask/mask.png differ diff --git a/routers/home.go b/routers/home.go index 2b852533a..85057c3a1 100755 --- a/routers/home.go +++ b/routers/home.go @@ -40,6 +40,7 @@ const ( tplExploreImages base.TplName = "explore/images" tplExploreExploreDataAnalysis base.TplName = "explore/data_analysis" tplHomeTerm base.TplName = "terms" + tplHomePrivacy base.TplName = "privacy" ) // Home render home page @@ -117,6 +118,10 @@ func Dashboard(ctx *context.Context) { ctx.Data["ChangePasscodeLink"] = setting.AppSubURL + "/user/change_password" ctx.SetCookie("redirect_to", setting.AppSubURL+ctx.Req.URL.RequestURI(), 0, setting.AppSubURL) ctx.Redirect(setting.AppSubURL + "/user/settings/change_password") + } else if setting.PhoneService.Enabled && ctx.User.IsActive && ctx.User.PhoneNumber == "" { + ctx.Data["Title"] = ctx.Tr("phone.bind_phone") + ctx.HTML(200, "user/auth/bind_phone") + return } else { user.Dashboard(ctx) } @@ -804,3 +809,7 @@ func RecommendHomeInfo(ctx *context.Context) { func HomeTerm(ctx *context.Context) { ctx.HTML(200, tplHomeTerm) } + +func HomePrivacy(ctx *context.Context) { + ctx.HTML(200, tplHomePrivacy) +} diff --git a/routers/init.go b/routers/init.go index eab513c78..7470b1922 100755 --- a/routers/init.go +++ b/routers/init.go @@ -10,6 +10,8 @@ import ( "strings" "time" + "code.gitea.io/gitea/modules/slideimage" + "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/migrations" "code.gitea.io/gitea/modules/auth/sso" @@ -57,6 +59,7 @@ func checkRunMode() { // NewServices init new services func NewServices() { setting.NewServices() + slideimage.InitSlideImage() if err := storage.Init(); err != nil { log.Fatal("storage init failed: %v", err) } diff --git a/routers/routes/routes.go b/routers/routes/routes.go index e4b2f06ca..55b65b40b 100755 --- a/routers/routes/routes.go +++ b/routers/routes/routes.go @@ -14,6 +14,8 @@ import ( "text/template" "time" + "code.gitea.io/gitea/modules/slideimage" + "code.gitea.io/gitea/routers/image" "code.gitea.io/gitea/routers/authentication" @@ -235,6 +237,10 @@ func NewMacaron() *macaron.Macaron { m.Use(captcha.Captchaer(captcha.Options{ SubURL: setting.AppSubURL, })) + m.Use(slideimage.SlideImager(slideimage.Options{ + SubURL: setting.AppSubURL, + SampleImages: setting.SlideImagesCount, + })) m.Use(session.Sessioner(session.Options{ Provider: setting.SessionConfig.Provider, ProviderConfig: setting.SessionConfig.ProviderConfig, @@ -336,6 +342,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("/all/dosearch/", routers.SearchApi) m.Post("/user/login/kanban", user.SignInPostAPI) m.Get("/home/term", routers.HomeTerm) + m.Get("/home/privacy", routers.HomePrivacy) m.Group("/explore", func() { m.Get("", func(ctx *context.Context) { ctx.Redirect(setting.AppSubURL + "/explore/repos") @@ -369,8 +376,11 @@ func RegisterRoutes(m *macaron.Macaron) { m.Group("/user", func() { m.Get("/login", user.SignIn) m.Get("/login/cloud_brain", user.SignInCloudBrain) - + m.Post("/login/cloud_brain", bindIgnErr(auth.SignInForm{}), user.SignInCloudBrainPost) m.Post("/login", bindIgnErr(auth.SignInForm{}), user.SignInPost) + + m.Get("/login/phone", user.SignInPhone) + m.Post("/login/phone", bindIgnErr(auth.PhoneNumberCodeForm{}), user.SignInPhonePost) m.Group("", func() { m.Combo("/login/openid"). Get(user.SignInOpenID). @@ -410,6 +420,10 @@ func RegisterRoutes(m *macaron.Macaron) { }, reqSignOut) m.Any("/user/events", reqSignIn, events.Events) + m.Get("/slideImage", user.CreateSlideImageInfo) + m.Post("/verifySlideImage", bindIgnErr(auth.SlideImageForm{}), user.VerifySlideImage) + m.Post("/sendVerifyCode", bindIgnErr(auth.PhoneNumberForm{}), user.SendVerifyCode) + m.Post("/bindPhone", reqSignIn, bindIgnErr(auth.PhoneNumberCodeForm{}), user.BindPhone) m.Group("/login/oauth", func() { m.Get("/authorize", bindIgnErr(auth.AuthorizationForm{}), user.AuthorizeOAuth) @@ -489,6 +503,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Get("/email2user", user.Email2User) m.Get("/recover_account", user.ResetPasswd) m.Post("/recover_account", user.ResetPasswdPost) + m.Post("/recover_account_by_phone",bindIgnErr(auth.ResetPassWordByPhoneForm{}), user.ResetPasswdByPhonePost) m.Get("/forgot_password", user.ForgotPasswd) m.Post("/forgot_password", user.ForgotPasswdPost) m.Post("/logout", user.SignOut) @@ -593,7 +608,6 @@ func RegisterRoutes(m *macaron.Macaron) { m.Post("/delete", admin.DeleteNotices) m.Post("/empty", admin.EmptyNotices) }) - }, adminReq) // ***** END: Admin ***** diff --git a/routers/user/auth.go b/routers/user/auth.go index 1edc5a0dd..7cd6472ff 100755 --- a/routers/user/auth.go +++ b/routers/user/auth.go @@ -8,9 +8,19 @@ package user import ( "errors" "fmt" + "github.com/gomodule/redigo/redis" "net/http" + "strconv" "strings" + "code.gitea.io/gitea/modules/slideimage" + + phoneService "code.gitea.io/gitea/services/phone" + + "code.gitea.io/gitea/modules/labelmsg" + + "code.gitea.io/gitea/modules/phone" + "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/auth/oauth2" @@ -38,16 +48,18 @@ const ( tplSignIn base.TplName = "user/auth/signin" // tplSignIn template for sign in page tplSignInCloudBrain base.TplName = "user/auth/signin_cloud_brain" + tplSignInPhone base.TplName = "user/auth/signin_phone" // tplSignUp template path for sign up page tplSignUp base.TplName = "user/auth/signup" // TplActivate template path for activate user - TplActivate base.TplName = "user/auth/activate" - tplForgotPassword base.TplName = "user/auth/forgot_passwd" - tplResetPassword base.TplName = "user/auth/reset_passwd" - tplTwofa base.TplName = "user/auth/twofa" - tplTwofaScratch base.TplName = "user/auth/twofa_scratch" - tplLinkAccount base.TplName = "user/auth/link_account" - tplU2F base.TplName = "user/auth/u2f" + TplActivate base.TplName = "user/auth/activate" + tplForgotPassword base.TplName = "user/auth/forgot_passwd" + tplForgotPasswordPhone base.TplName = "user/auth/forgot_passwd_phone" + tplResetPassword base.TplName = "user/auth/reset_passwd" + tplTwofa base.TplName = "user/auth/twofa" + tplTwofaScratch base.TplName = "user/auth/twofa_scratch" + tplLinkAccount base.TplName = "user/auth/link_account" + tplU2F base.TplName = "user/auth/u2f" ) // AutoSignIn reads cookie and try to auto-login. @@ -168,7 +180,7 @@ func SignInCloudBrain(ctx *context.Context) { return } - ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" + ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login/cloud_brain" ctx.Data["PageIsSignIn"] = true ctx.Data["PageIsCloudBrainLogin"] = true ctx.Data["EnableCloudBrain"] = true @@ -176,6 +188,50 @@ func SignInCloudBrain(ctx *context.Context) { ctx.HTML(200, tplSignInCloudBrain) } +func SignInPhone(ctx *context.Context) { + ctx.Data["Title"] = ctx.Tr("sign_in") + // Check auto-login. + if checkAutoLogin(ctx) { + return + } + + ctx.Data["PageIsPhoneLogin"] = true + + ctx.HTML(200, tplSignInPhone) +} + +func SignInPhonePost(ctx *context.Context, form auth.PhoneNumberCodeForm) { + ctx.Data["Title"] = ctx.Tr("sign_in") + ctx.Data["PageIsPhoneLogin"] = true + ctx.Data["IsCourse"] = ctx.QueryBool("course") + ctx.Data["EnableCloudBrain"] = true + + if ctx.HasError() { + ctx.HTML(200, tplSignInPhone) + return + } + + if !phoneService.IsVerifyCodeRight(strings.TrimSpace(form.PhoneNumber), strings.TrimSpace(form.VerifyCode)) { + ctx.RenderWithErr(ctx.Tr("phone.verify_code_fail"), tplSignInPhone, &form) + return + } + + u, err := models.GetUserByPhoneNumber(strings.TrimSpace(form.PhoneNumber)) + + if err != nil { + if models.IsErrUserNotExist(err) { + ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tplSignInPhone, &form) + log.Info("Failed authentication attempt for %s from %s", form.PhoneNumber, ctx.RemoteAddr()) + } else { + ctx.ServerError("UserSignIn", err) + } + return + } + models.SaveLoginInfoToDb(ctx.Req.Request, u) + + handleSignIn(ctx, u, form.Remember) +} + func SignInPostAPI(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("sign_in") UserName := ctx.Query("UserName") @@ -211,8 +267,7 @@ func SignInPostAPI(ctx *context.Context) { handleSignInFullNotRedirect(ctx, u, true, false) } -// SignInPost response for sign in request -func SignInPost(ctx *context.Context, form auth.SignInForm) { +func SignInPostCommon(ctx *context.Context, form auth.SignInForm) { ctx.Data["Title"] = ctx.Tr("sign_in") orderedOAuth2Names, oauth2Providers, err := models.GetActiveOAuth2Providers() @@ -223,11 +278,12 @@ func SignInPost(ctx *context.Context, form auth.SignInForm) { ctx.Data["OrderedOAuth2Names"] = orderedOAuth2Names ctx.Data["OAuth2Providers"] = oauth2Providers ctx.Data["Title"] = ctx.Tr("sign_in") - ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" + ctx.Data["PageIsSignIn"] = true - ctx.Data["PageIsLogin"] = true + ctx.Data["IsCourse"] = ctx.QueryBool("course") ctx.Data["EnableSSPI"] = models.IsSSPIEnabled() + ctx.Data["EnableCloudBrain"] = true if ctx.HasError() { ctx.HTML(200, tplSignIn) @@ -256,7 +312,9 @@ func SignInPost(ctx *context.Context, form auth.SignInForm) { ctx.HTML(200, "user/auth/prohibit_login") } } else { - ctx.ServerError("UserSignIn", err) + log.Warn("UserSignIn", err) + ctx.RenderWithErr(ctx.Tr("form.username_password_incorrect"), tplSignIn, &form) + log.Info("Failed authentication attempt for %s from %s", form.UserName, ctx.RemoteAddr()) } return } @@ -294,6 +352,20 @@ func SignInPost(ctx *context.Context, form auth.SignInForm) { ctx.Redirect(setting.AppSubURL + "/user/two_factor") } + +func SignInCloudBrainPost(ctx *context.Context, form auth.SignInForm) { + ctx.Data["PageIsCloudBrainLogin"] = true + ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login/cloud_brain" + SignInPostCommon(ctx,form) +} + +// SignInPost response for sign in request +func SignInPost(ctx *context.Context, form auth.SignInForm) { + ctx.Data["PageIsLogin"] = true + ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login" + SignInPostCommon(ctx,form) +} + // TwoFactor shows the user a two-factor authentication page. func TwoFactor(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("twofa") @@ -1252,11 +1324,26 @@ func SignUpPost(ctx *context.Context, cpt *captcha.Captcha, form auth.RegisterFo return } + if setting.PhoneService.Enabled { + phoneNumber := strings.TrimSpace(form.PhoneNumber) + verifyCode := strings.TrimSpace(form.VerifyCode) + if !phoneService.IsVerifyCodeRight(phoneNumber, verifyCode) { + ctx.RenderWithErr(ctx.Tr("phone.verify_code_fail"), tplSignUp, &form) + return + } + } + + if !form.Agree { + ctx.RenderWithErr(ctx.Tr("sign_up_agree_tips"), tplSignUp, &form) + return + } + u := &models.User{ - Name: form.UserName, - Email: form.Email, - Passwd: form.Password, - IsActive: !setting.Service.RegisterEmailConfirm, + Name: form.UserName, + Email: form.Email, + Passwd: form.Password, + PhoneNumber: strings.TrimSpace(form.PhoneNumber), + IsActive: !setting.Service.RegisterEmailConfirm, } if err := models.CreateUser(u); err != nil { switch { @@ -1427,18 +1514,30 @@ func ActivateEmail(ctx *context.Context) { // ForgotPasswd render the forget pasword page func ForgotPasswd(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("auth.forgot_password_title") + forgetType := ctx.Query("type") - if setting.MailService == nil { - ctx.Data["IsResetDisable"] = true - ctx.HTML(200, tplForgotPassword) - return - } + if forgetType == "phone" { + if !setting.PhoneService.Enabled { + ctx.Data["IsResetDisable"] = true + ctx.HTML(200, tplForgotPasswordPhone) + return + } + ctx.Data["IsResetRequest"] = true + ctx.HTML(200, tplForgotPasswordPhone) + } else { - email := ctx.Query("email") - ctx.Data["Email"] = email + if setting.MailService == nil { + ctx.Data["IsResetDisable"] = true + ctx.HTML(200, tplForgotPassword) + return + } - ctx.Data["IsResetRequest"] = true - ctx.HTML(200, tplForgotPassword) + email := ctx.Query("email") + ctx.Data["Email"] = email + + ctx.Data["IsResetRequest"] = true + ctx.HTML(200, tplForgotPassword) + } } // ForgotPasswdPost response for forget password request @@ -1454,12 +1553,16 @@ func ForgotPasswdPost(ctx *context.Context) { email := ctx.Query("email") ctx.Data["Email"] = email - u, err := models.GetUserByEmail(email) + u, err := models.GetUserByMainEmail(email) if err != nil { if models.IsErrUserNotExist(err) { ctx.Data["ResetPwdCodeLives"] = timeutil.MinutesToFriendly(setting.Service.ResetPwdCodeLives, ctx.Locale.Language()) - ctx.Data["IsResetSent"] = true - ctx.HTML(200, tplForgotPassword) + ctx.Data["IsResetSent"] = false + if used, _ := models.IsEmailUsed(email); used { + ctx.RenderWithErr(ctx.Tr("auth.email_not_main"), tplForgotPassword, nil) + } else { + ctx.RenderWithErr(ctx.Tr("auth.email_not_right"), tplForgotPassword, nil) + } return } @@ -1647,6 +1750,55 @@ func ResetPasswdPost(ctx *context.Context) { handleSignInFull(ctx, u, remember, true) } +func ResetPasswdByPhonePost(ctx *context.Context, form auth.ResetPassWordByPhoneForm) { + phoneNumber := strings.TrimSpace(form.PhoneNumber) + verifyCode := strings.TrimSpace(form.VerifyCode) + isRight := phoneService.IsVerifyCodeRight(phoneNumber, verifyCode) + if !isRight { + ctx.RenderWithErr(ctx.Tr("phone.verify_code_fail"), tplForgotPasswordPhone, form) + return + } + + passwd := strings.TrimSpace(form.Password) + if len(passwd) < setting.MinPasswordLength { + ctx.RenderWithErr(ctx.Tr("auth.password_too_short", setting.MinPasswordLength), tplForgotPasswordPhone, form) + return + } else if !password.IsComplexEnough(passwd) { + ctx.RenderWithErr(password.BuildComplexityError(ctx), tplForgotPasswordPhone, form) + return + } + + u, err := models.GetUserByPhoneNumber(phoneNumber) + if err != nil { + log.Error("fail to query by phone number", err) + ctx.RenderWithErr(ctx.Tr("phone.query_err", setting.MinPasswordLength), tplForgotPasswordPhone, form) + return + } + + if nil != ctx.User && u.ID != ctx.User.ID { + ctx.RenderWithErr(ctx.Tr("auth.reset_password_wrong_user", ctx.User.Email, u.Email), tplForgotPasswordPhone, form) + return + } + + if u.Rands, err = models.GetUserSalt(); err != nil { + ctx.ServerError("UpdateUser", err) + return + } + if u.Salt, err = models.GetUserSalt(); err != nil { + ctx.ServerError("UpdateUser", err) + return + } + u.HashPassword(passwd) + u.MustChangePassword = false + if err := models.UpdateUserCols(u, "must_change_password", "passwd", "rands", "salt"); err != nil { + ctx.ServerError("UpdateUser", err) + return + } + + handleSignInFull(ctx, u, form.Remember, true) + +} + // MustChangePassword renders the page to change a user's password func MustChangePassword(ctx *context.Context) { ctx.Data["Title"] = ctx.Tr("auth.must_change_password") @@ -1709,3 +1861,135 @@ func MustChangePasswordPost(ctx *context.Context, cpt *captcha.Captcha, form aut ctx.Redirect(setting.AppSubURL + "/") } + +func CreateSlideImageInfo(ctx *context.Context, slideImage *slideimage.SlideImage) { + id, _, _ := slideImage.CreateCode() + ctx.JSON(http.StatusOK, models.BaseMessage{0, id}) +} + +func VerifySlideImage(ctx *context.Context, slideImage *slideimage.SlideImage, form auth.SlideImageForm) { + if slideImage.Verify(form.SlideID, form.X) { + ctx.JSON(http.StatusOK, models.BaseOKMessage) + } else { + ctx.JSON(http.StatusOK, models.BaseErrorMessage("")) + } +} + +func BindPhone(ctx *context.Context, form auth.PhoneNumberCodeForm) { + if strings.TrimSpace(form.PhoneNumber) != "" && strings.TrimSpace(form.VerifyCode) != "" && phoneService.IsVerifyCodeRight(strings.TrimSpace(form.PhoneNumber), strings.TrimSpace(form.VerifyCode)) { + + ctx.User.PhoneNumber = strings.TrimSpace(form.PhoneNumber) + if err := models.UpdateUserSetting(ctx.User); err != nil { + ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.bind_phone_fail"))) + return + } + ctx.JSON(http.StatusOK, models.BaseOKMessage) + return + } + + ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.verify_code_fail"))) + +} + +func SendVerifyCode(ctx *context.Context, slideImage *slideimage.SlideImage, form auth.PhoneNumberForm) { + phoneNumber := strings.TrimSpace(form.PhoneNumber) + + if !phone.IsValidPhoneNumber(phoneNumber) { + ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.format_err"))) + return + } + + hasManual, err := slideImage.VerifyManual(form.SlideID) + if err != nil { + log.Warn("redis err", err) + ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.query_err"))) + return + + } + if !hasManual { + ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.query_err"))) + return + } + + if form.Mode != 2 { + has, err := models.IsUserByPhoneNumberExist(phoneNumber) + if err != nil { + log.Warn("sql err", err) + ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.query_err"))) + return + } + + if form.Mode==0 { //注册 + + if has { + ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.already_register"))) + return + } + } else { //手机号验证码登录 mode=1 忘记密码 mode=3 + if !has { + ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.not_register"))) + return + } + + } + + } else { + //修改手机号 mode=2 绑定手机 + u, err := models.GetUserByPhoneNumber(phoneNumber) + if err != nil && !models.IsErrUserNotExist(err) { + log.Warn("sql err", err) + ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.query_err"))) + return + } + + if u != nil { + + if u.ID == ctx.User.ID { //没有修改手机号 + ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.not_modify"))) + return + } else { //修改的手机已经被别的用户注册 + ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.already_register"))) + return + } + + } + } + + + redisConn := labelmsg.Get() + defer redisConn.Close() + + sendTimes, err := phoneService.GetPhoneNumberSendTimes(redisConn, phoneNumber) + if err != nil && err!=redis.ErrNil { + log.Warn("redis err", err) + ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.query_err"))) + return + + } + if sendTimes >= setting.PhoneService.MaxRetryTimes { + ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.max_times", strconv.Itoa(setting.PhoneService.MaxRetryTimes)))) + return + + } + + ttl, err := phoneService.GetPhoneCodeTTL(redisConn, phoneNumber) + if err != nil { + log.Warn("redis err", err) + ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.query_err"))) + return + + } + if setting.PhoneService.CodeTimeout-ttl < setting.PhoneService.RetryInterval { + ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.too_fast"))) + return + } + err = phoneService.SendVerifyCode(redisConn, phoneNumber) + if err != nil { + log.Warn("send code or redis err", err) + ctx.JSON(http.StatusOK, models.BaseErrorMessage(ctx.Tr("phone.query_err"))) + return + } + + ctx.JSON(http.StatusOK, models.BaseOKMessage) + +} diff --git a/routers/user/setting/profile.go b/routers/user/setting/profile.go index 0d788b422..998a0072b 100755 --- a/routers/user/setting/profile.go +++ b/routers/user/setting/profile.go @@ -12,6 +12,8 @@ import ( "io/ioutil" "strings" + phoneService "code.gitea.io/gitea/services/phone" + "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/auth" "code.gitea.io/gitea/modules/base" @@ -90,6 +92,18 @@ func ProfilePost(ctx *context.Context, form auth.UpdateProfileForm) { return } + if setting.PhoneService.Enabled && strings.TrimSpace(form.PhoneNumber) != "" { + if strings.TrimSpace(form.PhoneNumber) != ctx.User.PhoneNumber { + if phoneService.IsVerifyCodeRight(strings.TrimSpace(form.PhoneNumber), strings.TrimSpace(form.VerifyCode)) { + ctx.User.PhoneNumber = strings.TrimSpace(form.PhoneNumber) + } else { + ctx.Flash.Error(ctx.Tr("phone.verify_code_fail")) + ctx.Redirect(setting.AppSubURL + "/user/settings") + return + } + } + } + ctx.User.FullName = form.FullName ctx.User.KeepEmailPrivate = form.KeepEmailPrivate diff --git a/services/phone/phone.go b/services/phone/phone.go new file mode 100644 index 000000000..c4178d6f9 --- /dev/null +++ b/services/phone/phone.go @@ -0,0 +1,93 @@ +package phone + +import ( + "fmt" + "time" + + "code.gitea.io/gitea/modules/log" + + "code.gitea.io/gitea/modules/phone" + "code.gitea.io/gitea/modules/redis/redis_client" + "code.gitea.io/gitea/modules/setting" + + "github.com/gomodule/redigo/redis" +) + +//验证码存储前缀 使用时%s用手机号替代 +const CODE_PREFIX = "P_C:%s" + +//手机号发送验证码次数Hkey,%s对应日期, 存储在hset中,值是hashet,记录手机号和发送次数 +const TIMES_PREFIX = "P_T:%s" + +func GetPhoneNumberSendTimes(conn redis.Conn, phoneNumber string) (int, error) { + i, err := redis_client.HGET(conn, GetPhoneTimesHKey(), phoneNumber) + + return redis.Int(i, err) +} + +func GetPhoneCodeTTL(conn redis.Conn, phoneNumber string) (int, error) { + return redis_client.Ttl(conn, GetPhoneCodeKey(phoneNumber)) + +} +func SendVerifyCode(conn redis.Conn, phoneNumber string) error { + timesKey := GetPhoneTimesHKey() + exists, err := redis_client.EXISTS(conn, timesKey) + if err != nil { + return err + } + code := phone.GenerateVerifyCode(setting.PhoneService.VerifyCodeLength) + err = phone.SendVerifyCode(phoneNumber, code) + if err != nil { + return err + } + redis_client.SET(conn, GetPhoneCodeKey(phoneNumber), code, setting.PhoneService.CodeTimeout) + if !exists { + err = redis_client.HSETNX(conn, timesKey, phoneNumber, 1) + if err != nil { + return err + } + err = redis_client.EXPIRE(conn, timesKey, getRemainSecondOfDay(time.Now())) + if err != nil { + return err + } + + } else { + err = redis_client.HINCRBY(conn, timesKey, phoneNumber, 1) + + if err != nil { + return err + } + + } + return nil + +} + +func IsVerifyCodeRight(phoneNumer string, verifyCode string) bool { + if phoneNumer == "" { + return false + } + value, err := redis_client.Get(GetPhoneCodeKey(phoneNumer)) + if err != nil { + log.Warn("redis err", err) + return false + } else { + if value == "" { + return false + } + return value == verifyCode + } +} + +func GetPhoneCodeKey(phoneNumber string) string { + return fmt.Sprintf(CODE_PREFIX, phoneNumber) +} + +func GetPhoneTimesHKey() string { + today := time.Now().Format("2006-01-02") + return fmt.Sprintf(TIMES_PREFIX, today) +} + +func getRemainSecondOfDay(t time.Time) int { + return 86400 - 60*60*t.Hour() - 60*t.Minute() - t.Second() +} diff --git a/services/reward/limiter/config.go b/services/reward/limiter/config.go index d90cf0aac..d40cd728c 100644 --- a/services/reward/limiter/config.go +++ b/services/reward/limiter/config.go @@ -11,7 +11,7 @@ func GetSingleDailyPointLimitConfig() (*models.LimitConfigVO, error) { r, err := GetLimitConfigList(models.LimitConfigQueryOpts{ RefreshRate: models.PeriodDaily, Scope: models.LimitScopeSingleUser, - LimitCode: "", + LimitCode: models.SourceTypeAccomplishTask.Name(), LimitType: models.LimitTypeRewardPoint, }) if err != nil { @@ -27,7 +27,7 @@ func SetSingleDailyPointLimitConfig(limitNum int64, doer *models.User) error { l := &models.LimitConfigVO{ RefreshRate: models.PeriodDaily, Scope: models.LimitScopeSingleUser.Name(), - LimitCode: "", + LimitCode: models.SourceTypeAccomplishTask.Name(), LimitType: models.LimitTypeRewardPoint.Name(), LimitNum: limitNum, } diff --git a/services/reward/limiter/limiter.go b/services/reward/limiter/limiter.go index c303a1ef3..7e3607785 100644 --- a/services/reward/limiter/limiter.go +++ b/services/reward/limiter/limiter.go @@ -150,7 +150,7 @@ func (l *limiterRunner) limit(r models.LimitConfig) error { if usedNum > r.LimitNum { if usedNum-r.LimitNum >= l.amount { redis_client.IncrBy(redisKey, -1*l.amount) - return errors.New(fmt.Sprintf("%s:over limit", r.Tittle)) + return errors.New(fmt.Sprintf("over limit,congfigId=%d", r.ID)) } switch l.rejectPolicy { case models.FillUp: @@ -161,7 +161,7 @@ func (l *limiterRunner) limit(r models.LimitConfig) error { return nil case models.JustReject: redis_client.IncrBy(redisKey, -1*l.amount) - return errors.New(fmt.Sprintf("%s:over limit", r.Tittle)) + return errors.New(fmt.Sprintf("over limit,congfigId=%d", r.ID)) case models.PermittedOnce: l.resultMap[l.index] = newLimitResult(false, l.amount, l.amount) return nil diff --git a/templates/admin/user/list.tmpl b/templates/admin/user/list.tmpl index 72b7ccd19..dc16caea8 100644 --- a/templates/admin/user/list.tmpl +++ b/templates/admin/user/list.tmpl @@ -20,6 +20,7 @@ {{.i18n.Tr "admin.users.name"}} {{.i18n.Tr "email"}} {{.i18n.Tr "admin.users.activated"}} + {{.i18n.Tr "admin.users.bind_phone"}} {{.i18n.Tr "admin.users.admin"}} {{.i18n.Tr "admin.users.restricted"}} {{.i18n.Tr "admin.users.repos"}} @@ -35,6 +36,7 @@ {{.Name}} {{.Email}} + {{.NumRepos}} diff --git a/templates/privacy.tmpl b/templates/privacy.tmpl new file mode 100644 index 000000000..81ecb199a --- /dev/null +++ b/templates/privacy.tmpl @@ -0,0 +1,92 @@ +{{template "base/head_home" .}} + +
+

OpenI启智社区AI协作平台隐私协议

+
+

+ OpenI启智社区AI协作平台作为新一代人工智能领域开源开放开发协作平台,不仅为用户提供代码托管与数据集管理等服务,同时提供开发者所需的计算算力资源,一个良好的开发环境对用户、组织和项目都尤为重要。 +

+

+ 为切实保护平台用户隐私权,优化用户体验,OpenI启智社区根据现行法规及政策,制定本《OpenI启智社区AI协作平台隐私协议》。本协议将详细说明OpenI启智社区AI协作平台在获取、管理及保护用户个人信息方面的政策及措施。本协议适用于OpenI启智社区AI协作平台向您提供的所有服务,无论您是通过计算机设备、移动终端或其他设备获得本平台的服务。 +

+

+ 一、个人信息的收集 +

+

+ 您已知悉且同意,在您注册本平台帐号或使用平台提供的服务时,本平台将记录您提供的相关个人信息,如:账号ID、密码、邮箱、手机号码等,上述个人信息是您获得平台提供服务的基础。 +

+

+ 在您使用本平台前,我们会引导您阅读本协议,并在您接受本协议的基础上,获得您的相关个人信息。如果您不同意提供个人信息,您将无法使用本平台的全部或部分功能和服务。 +

+

+ 二、个人信息的存储 +

+

+ 本平台会采取合适的安全措施和技术手段存储及保护您的个人信息,以防止丢失、被误用、受到未授权访问或泄漏、被篡改或毁坏。您的个人信息存放在有密码控制的位于中国境内的服务器中,访问均是受到限制的。 +

+

+ 根据本条款的规定,我们仅允许有必要知晓这些信息的平台员工等第三方访问个人信息,并要求他们履行相应的保密义务。 +

+

+ 三、个人信息的管理 +

+

+ 在完成平台账号注册并进行身份验证后,您可以查阅、修改、删除所提交的个人信息。一般情况下,您可随时浏览、修改、删除自己提交的信息,但出于安全性和身份识别的考虑,您可能无法修改注册时提供的某些初始注册信息及验证信息。 +

+

+ 您有权自主更新或更正您的个人信息,或授权本平台工作人员进行信息更新、更正。在您进行信息更新或更正之前,我们会首先验证您的身份。 +

+

+ 您可以自主注销本平台账号,注销后本平台不再收集您的个人信息,注销账号是不可逆的行为,一旦完成注销,本平台将删除有关您账户的一切信息。 +

+

+ 四、个人信息的保护 +

+

+ 本平台将尽一切合理努力保护其获得的用户个人信息。为防止用户个人信息在意外的、未经授权的情况下被非法访问、复制、修改、传送、遗失、破坏、处理或使用,本平台已经并将继续采取以下措施保护您的个人信息:
+

+
    +
  1. 以适当的方式对用户的个人信息进行加密护理,并通过隔离技术进行隔离;
  2. +
  3. 在适当的位置使用密码对用户个人信息进行保护;
  4. +
  5. 设立严格的数据使用和访问制度,采用严格的数据访问权限控制保护个人信息;
  6. +
  7. 其他合理措施。
  8. +
+

+ 尽管已经采取了上述合理有效措施,并遵守相关法律规定要求的标准,但本平台仍然无法保证您的个人信息通过不安全途径进行交流时的安全性。因此,用户个人应采取积极措施保证个人信息的安全,如:定期修改账号密码,不将自己的账号密码等个人信息透露给他人。 +

+

+ 您知悉,本平台提供的个人信息保护措施仅适用于OpenI启智社区AI协作平台,一旦您离开本平台,浏览或使用其他网站、服务及内容资源,本平台即没有能力及义务保护您在本平台以外的网站提交的任何个人信息,无论您登录或浏览上述网站是否基于本平台的链接或引导。 +

+

+ 五、个人信息的使用和对外提供 +

+

+ 未经您本人允许,平台不会向任何第三方公开您的个人信息,下列情形除外: +

+
    +
  1. 本平台已经取得您或您监护人的授权或同意;
  2. +
  3. 司法机关或行政机关基于法定程序要求平台披露的;
  4. +
  5. 本平台为维护自身合法权益而向用户提起诉讼或仲裁时;
  6. +
  7. 根据您与本平台相关服务条款、应用许可使用协议的约定;
  8. +
  9. 在法律允许的范围内,为保障本平台、平台用户以及社会公共利益免受损害时;
  10. +
  11. 法律法规规定的其他情形。
  12. +
+

+ 六、对未成年人个人信息的特别保护 +

+

+ 本平台非常重视对未成年人个人信息的保护。若您是18周岁以下的未成年人,在使用本平台的服务前,应确保事先取得监护人的同意,如您在平台上申请注册账号,本平台将默认为您已得到前述同意。本平台将根据国家相关法律法规及本协议的规定保护未成年人的个人信息。 +

+

+ 七、隐私协议的修改 +

+

+ 平台有权随时修改《OpenI启智社区AI协作平台隐私协议》的任何条款,一旦《OpenI启智社区AI协作平台隐私协议》内容发生变动,本平台将会在OpenI启智社区网站首页公告上公布修改后的政策,该公布行为视为本平台已经通知您修改内容。如果您不同意本平台对《OpenI启智社区AI协作平台隐私协议》相关条款所做的修改,您有权停止使用平台服务。如果您继续使用平台服务,则视为您接受平台对协议相关条款所做的修改。 +

+ +
+{{template "base/footer" .}} diff --git a/templates/repo/modelarts/trainjob/show.tmpl b/templates/repo/modelarts/trainjob/show.tmpl index 14814a9f7..bd542d912 100755 --- a/templates/repo/modelarts/trainjob/show.tmpl +++ b/templates/repo/modelarts/trainjob/show.tmpl @@ -535,13 +535,10 @@
/
-
+
-
- - {{$.i18n.Tr "repo.file_limit_100"}} -
+
diff --git a/templates/user/auth/bind_phone.tmpl b/templates/user/auth/bind_phone.tmpl new file mode 100644 index 000000000..754fe1e6a --- /dev/null +++ b/templates/user/auth/bind_phone.tmpl @@ -0,0 +1,79 @@ +{{template "base/head" .}} +
+
+
+
+ {{.CsrfTokenHtml}} +

+ {{.i18n.Tr "phone.please_bind_your_mobile_number"}} +

+
+ {{template "base/alert" .}} + + {{if .EnablePhone }} +
+
+ {{template "user/auth/phone_verify" .}} +
+
+ + {{end}} +
+
+ + +
+
+
+ +
+
+
+{{template "base/footer" .}} diff --git a/templates/user/auth/forgot_passwd.tmpl b/templates/user/auth/forgot_passwd.tmpl index 1e061763a..c92759f72 100644 --- a/templates/user/auth/forgot_passwd.tmpl +++ b/templates/user/auth/forgot_passwd.tmpl @@ -1,4 +1,14 @@ {{template "base/head" .}} +
@@ -14,8 +24,9 @@ {{else if .IsResetRequest}}
- +
+
{{.i18n.Tr "auth.please_enter_main_email_tips"}}
diff --git a/templates/user/auth/forgot_passwd_phone.tmpl b/templates/user/auth/forgot_passwd_phone.tmpl new file mode 100644 index 000000000..c6b6f2256 --- /dev/null +++ b/templates/user/auth/forgot_passwd_phone.tmpl @@ -0,0 +1,45 @@ +{{template "base/head" .}} + +
+
+
+
+ {{.CsrfTokenHtml}} +

+ {{.i18n.Tr "auth.forgot_password_title"}} +

+
+ {{template "base/alert" .}} + + {{if .EnablePhone }} +
+
+ {{template "user/auth/phone_verify" .}} +
+
+ + {{end}} +
+
+ + +
+
+
+
+
+
+{{template "base/footer" .}} diff --git a/templates/user/auth/phone_verify.tmpl b/templates/user/auth/phone_verify.tmpl new file mode 100644 index 000000000..e6b3ac8fb --- /dev/null +++ b/templates/user/auth/phone_verify.tmpl @@ -0,0 +1,75 @@ + +
+
+
+ {{.i18n.Tr "phone.phone_number"}} +
+
+ +
+ +
+
+
+
+
+
{{.i18n.Tr "phone.drag_the_slider_to_fill_the_puzzle"}}
+
+
+ + + +
+
+
+
+
+
+
+
+
+ {{.i18n.Tr "phone.mobile_phone_verification_code"}} +
+
+ +
+
+
{{.i18n.Tr "phone.get_verification_code"}}
+
+
+
+
+ {{.i18n.Tr "phone.new_login_password"}} +
+
+ +
+
+
+
+
+
+ + +
+
+
+
+ + diff --git a/templates/user/auth/signin_inner.tmpl b/templates/user/auth/signin_inner.tmpl index bdcd6a5c6..32ce80dc5 100644 --- a/templates/user/auth/signin_inner.tmpl +++ b/templates/user/auth/signin_inner.tmpl @@ -28,7 +28,7 @@
-
+
{{if .IsCourse}}
{{else}} @@ -38,7 +38,7 @@
- +
{{if or (not .DisablePassword) .LinkAccountMode}} diff --git a/templates/user/auth/signin_navbar.tmpl b/templates/user/auth/signin_navbar.tmpl index e4ad2bd9c..275d9085d 100755 --- a/templates/user/auth/signin_navbar.tmpl +++ b/templates/user/auth/signin_navbar.tmpl @@ -1,10 +1,15 @@ -{{if or .EnableOpenIDSignIn .EnableSSPI .EnableCloudBrain}} +{{if or .EnablePhone .EnableOpenIDSignIn .EnableSSPI .EnableCloudBrain}}