diff --git a/.gitignore b/.gitignore index 66fd13c..b32b330 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,8 @@ # Dependency directories (remove the comment below to include it) # vendor/ + +# IDE +.vscode/ +target/ +__debug_bin diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3c7f77a --- /dev/null +++ b/Makefile @@ -0,0 +1,31 @@ +APP_NAME = casaos-user-service +APP_NAME_SHORT = user-service + +TARGET_DIR = target +BUILD_DIR = $(TARGET_DIR)/build + +INSTALL_ROOT = / + +all: $(TARGET_DIR) + +$(BUILD_DIR): clean + mkdir -pv $(BUILD_DIR)/usr/bin + +$(APP_NAME): $(BUILD_DIR) + go build -v -o $(BUILD_DIR)/usr/bin/$(APP_NAME) + +$(TARGET_DIR): $(APP_NAME) + cp -rv build $(TARGET_DIR) + +clean: + rm -rfv $(TARGET_DIR) + +install: + cp -rv $(BUILD_DIR)/* $(INSTALL_ROOT) + systemctl enable --now $(APP_NAME).service + +uninstall: + systemctl disable --now $(APP_NAME).service + rm -v $(INSTALL_ROOT)/etc/casaos/$(APP_NAME_SHORT).conf + rm -v $(INSTALL_ROOT)/usr/bin/$(APP_NAME) + rm -v $(INSTALL_ROOT)/usr/lib/systemd/system/$(APP_NAME).service diff --git a/README.md b/README.md index 054c90e..1c61cb4 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ -# CasaOS-UserService \ No newline at end of file +# CasaOS-UserService + +User Service provides user management functionalities to CasaOS. diff --git a/build/etc/casaos/user-service.conf b/build/etc/casaos/user-service.conf new file mode 100644 index 0000000..a146555 --- /dev/null +++ b/build/etc/casaos/user-service.conf @@ -0,0 +1,9 @@ +[common] +RuntimePath=/var/run/casaos + +[app] +LogPath = /var/log/casaos +LogSaveName = user-service +LogFileExt = log +DBPath = /var/lib/casaos +UserDataPath = /var/lib/casaos diff --git a/build/usr/lib/systemd/system/casaos-user-service.service b/build/usr/lib/systemd/system/casaos-user-service.service new file mode 100644 index 0000000..4a46e82 --- /dev/null +++ b/build/usr/lib/systemd/system/casaos-user-service.service @@ -0,0 +1,12 @@ +[Unit] +After=casaos-gateway.service +ConditionFileNotEmpty=/etc/casaos/user-service.conf +Description=CasaOS User Service + +[Service] +ExecStart=/usr/bin/casaos-user-service -c /etc/casaos/user-service.conf +PIDFile=/var/run/casaos/user-service.pid +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..d60fed9 --- /dev/null +++ b/go.mod @@ -0,0 +1,47 @@ +module github.com/IceWhaleTech/CasaOS-UserService + +go 1.18 + +require ( + github.com/IceWhaleTech/CasaOS-Common v0.0.0-20220804224534-081d69c201cc + github.com/IceWhaleTech/CasaOS-Gateway v0.0.0-20220804231126-285796241a3b + github.com/gin-contrib/gzip v0.0.6 + github.com/gin-gonic/gin v1.8.1 + github.com/satori/go.uuid v1.2.0 + github.com/tidwall/gjson v1.14.1 + go.uber.org/zap v1.21.0 + gopkg.in/ini.v1 v1.66.6 + gorm.io/driver/sqlite v1.3.6 + gorm.io/gorm v1.23.8 +) + +require ( + github.com/benbjohnson/clock v1.3.0 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.0 // indirect + github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/go-playground/validator/v10 v10.11.0 // indirect + github.com/goccy/go-json v0.9.10 // indirect + github.com/golang-jwt/jwt/v4 v4.4.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-sqlite3 v1.14.14 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.2 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect + github.com/ugorji/go/codec v1.2.7 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.8.0 // indirect + golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect + golang.org/x/net v0.0.0-20220726230323-06994584191e // indirect + golang.org/x/sys v0.0.0-20220727055044-e65921a090b8 // indirect + golang.org/x/text v0.3.7 // indirect + google.golang.org/protobuf v1.28.0 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..3cb538d --- /dev/null +++ b/go.sum @@ -0,0 +1,172 @@ +github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0= +github.com/IceWhaleTech/CasaOS-Common v0.0.0-20220804224534-081d69c201cc h1:PVQLOa4Ao9vmVDi8bontpDQct81oE4NQJ72XEuo/7PU= +github.com/IceWhaleTech/CasaOS-Common v0.0.0-20220804224534-081d69c201cc/go.mod h1:2WIgP2lnFQ/1TfCnXW00JJIKm3coLyV3WqAaTCAYY28= +github.com/IceWhaleTech/CasaOS-Gateway v0.0.0-20220804231126-285796241a3b h1:IiMCqvGelQLGTX151gqVwrzoPQVJy8Q2JAvkhjiQ6tY= +github.com/IceWhaleTech/CasaOS-Gateway v0.0.0-20220804231126-285796241a3b/go.mod h1:jcURlZtPPQJJvfIW4ZgDTtpFfak7bPTvKZUxWxf62M8= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4= +github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= +github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= +github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.9.10 h1:hCeNmprSNLB8B8vQKWl6DpuH0t60oEs+TAk9a7CScKc= +github.com/goccy/go-json v0.9.10/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs= +github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.14 h1:qZgc/Rwetq+MtyE18WhzjokPD93dNqLGNT3QJuLvBGw= +github.com/mattn/go-sqlite3 v1.14.14/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= +github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw= +github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo= +github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8= +go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8= +go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220726230323-06994584191e h1:wOQNKh1uuDGRnmgF0jDxh7ctgGy/3P4rYWQRVJD4/Yg= +golang.org/x/net v0.0.0-20220726230323-06994584191e/go.mod h1:AaygXjzTFtRAg2ttMY5RMuhpJ3cNnI0XpyFJD1iQRSM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220727055044-e65921a090b8 h1:dyU22nBWzrmTQxtNrr4dzVOvaw35nUYE279vF9UmsI8= +golang.org/x/sys v0.0.0-20220727055044-e65921a090b8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI= +gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/sqlite v1.3.6 h1:Fi8xNYCUplOqWiPa3/GuCeowRNBRGTf62DEmhMDHeQQ= +gorm.io/driver/sqlite v1.3.6/go.mod h1:Sg1/pvnKtbQ7jLXxfZa+jSHvoX8hoZA8cn4xllOMTgE= +gorm.io/gorm v1.23.4/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +gorm.io/gorm v1.23.8 h1:h8sGJ+biDgBA1AD1Ha9gFCx7h8npU7AsLdlkX0n2TpE= +gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= diff --git a/main.go b/main.go new file mode 100644 index 0000000..9680801 --- /dev/null +++ b/main.go @@ -0,0 +1,86 @@ +package main + +import ( + "flag" + "fmt" + "log" + "net" + "net/http" + + "github.com/IceWhaleTech/CasaOS-Common/utils/logger" + "github.com/IceWhaleTech/CasaOS-Gateway/common" + "github.com/IceWhaleTech/CasaOS-UserService/pkg/config" + "github.com/IceWhaleTech/CasaOS-UserService/pkg/sqlite" + "github.com/IceWhaleTech/CasaOS-UserService/pkg/utils/encryption" + "github.com/IceWhaleTech/CasaOS-UserService/pkg/utils/random" + "github.com/IceWhaleTech/CasaOS-UserService/route" + "github.com/IceWhaleTech/CasaOS-UserService/service" + "gorm.io/gorm" +) + +const localhost = "127.0.0.1" + +var ( + sqliteDB *gorm.DB + configFlag = flag.String("c", "", "config address") + dbFlag = flag.String("db", "", "db path") + resetUser = flag.Bool("ru", false, "reset user") + user = flag.String("user", "", "user name") +) + +func init() { + flag.Parse() + config.InitSetup(*configFlag) + + logger.LogInit(config.AppInfo.LogPath, config.AppInfo.LogSaveName, config.AppInfo.LogFileExt) + + if len(*dbFlag) == 0 { + *dbFlag = config.AppInfo.DBPath + "/db" + } + + sqliteDB = sqlite.GetDb(*dbFlag) + service.MyService = service.NewService(sqliteDB, config.CommonInfo.RuntimePath) +} + +func main() { + r := route.InitRouter() + + if *resetUser { + if user == nil || len(*user) == 0 { + fmt.Println("user is empty") + return + } + userData := service.MyService.User().GetUserAllInfoByName(*user) + if userData.Id == 0 { + fmt.Println("user not exist") + return + } + password := random.RandomString(6, false) + userData.Password = encryption.GetMD5ByStr(password) + service.MyService.User().UpdateUserPassword(userData) + fmt.Println("User reset successful") + fmt.Println("UserName:" + userData.Username) + fmt.Println("Password:" + password) + return + } + + listener, err := net.Listen("tcp", net.JoinHostPort(localhost, "0")) + if err != nil { + panic(err) + } + + err = service.MyService.Gateway().CreateRoute(&common.Route{ + Path: "/v1/users", + Target: "http://" + listener.Addr().String(), + }) + + if err != nil { + panic(err) + } + + log.Printf("user service listening on %s", listener.Addr().String()) + err = http.Serve(listener, r) + if err != nil { + panic(err) + } +} diff --git a/middleware/gin.go b/middleware/gin.go new file mode 100644 index 0000000..c273808 --- /dev/null +++ b/middleware/gin.go @@ -0,0 +1,61 @@ +/* + * @Author: LinkLeong link@icewhale.com + * @Date: 2021-10-08 10:29:08 + * @LastEditors: LinkLeong + * @LastEditTime: 2022-07-22 11:06:07 + * @Description: + * @Website: https://www.casaos.io + * Copyright (c) 2022 by icewhale, All Rights Reserved. + */ +package middleware + +import ( + "fmt" + "net/http" + "strings" + + "github.com/IceWhaleTech/CasaOS-Common/utils/logger" + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +func Cors() gin.HandlerFunc { + return func(c *gin.Context) { + method := c.Request.Method + + c.Header("Access-Control-Allow-Origin", "*") + c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE,UPDATE") + // 允许跨域设置可以返回其他子段,可以自定义字段 + c.Header("Access-Control-Allow-Headers", "Authorization, Content-Length, X-CSRF-Token, Token,session,Language,Content-Type,Access-Control-Allow-Origin,Access-Control-Allow-Headers,Access-Control-Allow-Methods,Connection,Host,Origin,Referer,User-Agent,X-Requested-With") + // 允许浏览器(客户端)可以解析的头部 (重要) + c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers") + // c.Writer.Header().Set("Access-Control-Allow-Headers", "Accept, Authorization, Content-Type, Content-Length, X-CSRF-Token, Token, session, Origin, Host, Connection, Accept-Encoding, Accept-Language, X-Requested-With") + // 设置缓存时间 + c.Header("Access-Control-Max-Age", "172800") + c.Header("Access-Control-Allow-Credentials", "true") + c.Set("Content-Type", "application/json") + //} + + // 允许类型校验 + if method == "OPTIONS" { + c.JSON(http.StatusOK, "ok!") + } + + defer func() { + if err := recover(); err != nil { + fmt.Println(err) + } + }() + + c.Next() + } +} + +func WriteLog() gin.HandlerFunc { + return func(c *gin.Context) { + if !strings.Contains(c.Request.URL.String(), "password") { + logger.Info("request:", zap.Any("path", c.Request.URL.String()), zap.Any("param", c.Params), zap.Any("query", c.Request.URL.Query()), zap.Any("method", c.Request.Method)) + c.Next() + } + } +} diff --git a/model/sys_common.go b/model/sys_common.go new file mode 100644 index 0000000..9d56b36 --- /dev/null +++ b/model/sys_common.go @@ -0,0 +1,19 @@ +package model + +type CommonModel struct { + RuntimePath string +} + +type APPModel struct { + LogPath string + LogSaveName string + LogFileExt string + UserDataPath string + DBPath string +} + +type Result struct { + Success int `json:"success" example:"200"` + Message string `json:"message" example:"ok"` + Data interface{} `json:"data" example:"返回结果"` +} diff --git a/model/system_model/verify_information.go b/model/system_model/verify_information.go new file mode 100644 index 0000000..7404202 --- /dev/null +++ b/model/system_model/verify_information.go @@ -0,0 +1,16 @@ +/* + * @Author: LinkLeong link@icewhale.com + * @Date: 2022-06-15 11:30:47 + * @LastEditors: LinkLeong + * @LastEditTime: 2022-06-23 18:40:40 + * @Description: + * @Website: https://www.casaos.io + * Copyright (c) 2022 by icewhale, All Rights Reserved. + */ +package system_model + +type VerifyInformation struct { + RefreshToken string `json:"refresh_token"` + AccessToken string `json:"access_token"` + ExpiresAt int64 `json:"expires_at"` +} diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..ed32f4b --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,15 @@ +/* + * @Author: LinkLeong link@icewhale.com + * @Date: 2021-09-30 18:18:14 + * @LastEditors: LinkLeong + * @LastEditTime: 2022-06-21 11:09:30 + * @FilePath: /CasaOS/pkg/config/config.go + * @Description: + * @Website: https://www.casaos.io + * Copyright (c) 2022 by icewhale, All Rights Reserved. + */ +package config + +const ( + USERCONFIGURL = "/etc/casaos/casaos.conf" +) diff --git a/pkg/config/init.go b/pkg/config/init.go new file mode 100644 index 0000000..4045b32 --- /dev/null +++ b/pkg/config/init.go @@ -0,0 +1,52 @@ +package config + +import ( + "fmt" + "log" + "os" + + "github.com/IceWhaleTech/CasaOS-UserService/model" + "gopkg.in/ini.v1" +) + +// models with default values + +var CommonInfo = &model.CommonModel{ + RuntimePath: "/var/run/casaos", +} + +var AppInfo = &model.APPModel{ + DBPath: "/var/lib/casaos", + UserDataPath: "/var/lib/casaos", + LogPath: "/var/log/casaos", + LogSaveName: "user", + LogFileExt: "log", +} + +var Cfg *ini.File + +func InitSetup(config string) { + + var configDir = USERCONFIGURL + if len(config) > 0 { + configDir = config + } + + var err error + + Cfg, err = ini.Load(configDir) + if err != nil { + fmt.Printf("Fail to read file: %v", err) + os.Exit(1) + } + + mapTo("common", CommonInfo) + mapTo("app", AppInfo) +} + +func mapTo(section string, v interface{}) { + err := Cfg.Section(section).MapTo(v) + if err != nil { + log.Fatalf("Cfg.MapTo %s err: %v", section, err) + } +} diff --git a/pkg/sqlite/db.go b/pkg/sqlite/db.go new file mode 100644 index 0000000..114fe46 --- /dev/null +++ b/pkg/sqlite/db.go @@ -0,0 +1,59 @@ +/* + * @Author: LinkLeong link@icewhale.com + * @Date: 2022-05-13 18:15:46 + * @LastEditors: LinkLeong + * @LastEditTime: 2022-07-11 18:10:53 + * @Description: + * @Website: https://www.casaos.io + * Copyright (c) 2022 by icewhale, All Rights Reserved. + */ +package sqlite + +import ( + "time" + + "github.com/IceWhaleTech/CasaOS-Common/utils/logger" + "github.com/IceWhaleTech/CasaOS-UserService/pkg/utils/file" + model2 "github.com/IceWhaleTech/CasaOS-UserService/service/model" + "go.uber.org/zap" + "gorm.io/driver/sqlite" + "gorm.io/gorm" +) + +var gdb *gorm.DB + +func GetDb(dbPath string) *gorm.DB { + if gdb != nil { + return gdb + } + // Refer https://github.com/go-sql-driver/mysql#dsn-data-source-name + // dsn := fmt.Sprintf("%v:%v@tcp(%v:%v)/%v?charset=utf8mb4&parseTime=True&loc=Local", m.User, m.PWD, m.IP, m.Port, m.DBName) + // db, err := gorm.Open(mysql2.Open(dsn), &gorm.Config{}) + file.IsNotExistMkDir(dbPath) + db, err := gorm.Open(sqlite.Open(dbPath+"/user.db"), &gorm.Config{}) + c, _ := db.DB() + c.SetMaxIdleConns(10) + c.SetMaxOpenConns(100) + c.SetConnMaxIdleTime(time.Second * 1000) + if err != nil { + logger.Error("sqlite connect error", zap.Any("db connect error", err)) + panic("sqlite connect error") + } + gdb = db + + db.Exec(`alter table o_user rename to old_user; + + create table o_users ( id integer primary key,username text,password text,role text,email text,nickname text,avatar text,description text,created_at datetime,updated_at datetime); + + insert into o_users select id,user_name,password,role,email,nick_name,avatar,description,created_at,updated_at from old_user; + + drop table old_user; + drop table o_user; + `) + + err = db.AutoMigrate(model2.UserDBModel{}) + if err != nil { + logger.Error("check or create db error", zap.Any("error", err)) + } + return db +} diff --git a/pkg/utils/encryption/md5_helper.go b/pkg/utils/encryption/md5_helper.go new file mode 100644 index 0000000..1a142e6 --- /dev/null +++ b/pkg/utils/encryption/md5_helper.go @@ -0,0 +1,21 @@ +/* + * @Author: LinkLeong link@icewhale.com + * @Date: 2022-06-14 14:33:25 + * @LastEditors: LinkLeong + * @LastEditTime: 2022-06-14 14:33:49 + * @Description: + * @Website: https://www.casaos.io + * Copyright (c) 2022 by icewhale, All Rights Reserved. + */ +package encryption + +import ( + "crypto/md5" + "encoding/hex" +) + +func GetMD5ByStr(str string) string { + h := md5.New() + h.Write([]byte(str)) + return hex.EncodeToString(h.Sum(nil)) +} diff --git a/pkg/utils/file/file.go b/pkg/utils/file/file.go new file mode 100644 index 0000000..9948a07 --- /dev/null +++ b/pkg/utils/file/file.go @@ -0,0 +1,146 @@ +package file + +import ( + "io" + "io/ioutil" + "os" + "path" + "strings" +) + +// GetExt get the file ext +func GetExt(fileName string) string { + return path.Ext(fileName) +} + +func CheckNotExist(src string) bool { + _, err := os.Stat(src) + + return os.IsNotExist(err) +} + +// IsNotExistMkDir create a directory if it does not exist +func IsNotExistMkDir(src string) error { + if notExist := CheckNotExist(src); notExist { + if err := MkDir(src); err != nil { + return err + } + } + + return nil +} + +// MkDir create a directory +func MkDir(src string) error { + err := os.MkdirAll(src, os.ModePerm) + if err != nil { + return err + } + os.Chmod(src, 0777) + + return nil +} + +// IsNotExistMkDir create a directory if it does not exist +func IsNotExistCreateFile(src string) error { + if notExist := CheckNotExist(src); notExist { + if err := CreateFile(src); err != nil { + return err + } + } + + return nil +} + +func Exists(path string) bool { + _, err := os.Stat(path) //os.Stat获取文件信息 + if err != nil { + return os.IsExist(err) + } + return true +} + +func CreateFile(path string) error { + file, err := os.Create(path) + if err != nil { + return err + } + defer file.Close() + return nil +} + +func ReadFullFile(path string) []byte { + file, err := os.Open(path) + if err != nil { + return []byte("") + } + defer file.Close() + content, err := ioutil.ReadAll(file) + if err != nil { + return []byte("") + } + return content +} + +/** + * @description: + * @param {*} src + * @param {*} dst + * @param {string} style + * @return {*} + * @method: + * @router: + */ +func CopySingleFile(src, dst, style string) error { + var err error + var srcfd *os.File + var dstfd *os.File + var srcinfo os.FileInfo + + if Exists(dst) { + if style == "skip" { + return nil + } else { + os.Remove(dst) + } + } + + if srcfd, err = os.Open(src); err != nil { + return err + } + defer srcfd.Close() + + if dstfd, err = os.Create(dst); err != nil { + return err + } + defer dstfd.Close() + + if _, err = io.Copy(dstfd, srcfd); err != nil { + return err + } + if srcinfo, err = os.Stat(src); err != nil { + return err + } + return os.Chmod(dst, srcinfo.Mode()) +} + +func WriteToPath(data []byte, path, name string) error { + fullPath := path + if strings.HasSuffix(path, "/") { + fullPath += name + } else { + fullPath += "/" + name + } + IsNotExistCreateFile(fullPath) + file, err := os.OpenFile(fullPath, + os.O_WRONLY|os.O_TRUNC|os.O_CREATE, + 0666, + ) + if err != nil { + return err + } + defer file.Close() + _, err = file.Write(data) + + return err +} diff --git a/pkg/utils/file/image.go b/pkg/utils/file/image.go new file mode 100644 index 0000000..105a59e --- /dev/null +++ b/pkg/utils/file/image.go @@ -0,0 +1,182 @@ +package file + +import ( + "errors" + "net/http" + "os" + "path/filepath" + "strings" +) + +func ImageExtArray() []string { + + ext := []string{ + "ase", + "art", + "bmp", + "blp", + "cd5", + "cit", + "cpt", + "cr2", + "cut", + "dds", + "dib", + "djvu", + "egt", + "exif", + "gif", + "gpl", + "grf", + "icns", + "ico", + "iff", + "jng", + "jpeg", + "jpg", + "jfif", + "jp2", + "jps", + "lbm", + "max", + "miff", + "mng", + "msp", + "nitf", + "ota", + "pbm", + "pc1", + "pc2", + "pc3", + "pcf", + "pcx", + "pdn", + "pgm", + "PI1", + "PI2", + "PI3", + "pict", + "pct", + "pnm", + "pns", + "ppm", + "psb", + "psd", + "pdd", + "psp", + "px", + "pxm", + "pxr", + "qfx", + "raw", + "rle", + "sct", + "sgi", + "rgb", + "int", + "bw", + "tga", + "tiff", + "tif", + "vtf", + "xbm", + "xcf", + "xpm", + "3dv", + "amf", + "ai", + "awg", + "cgm", + "cdr", + "cmx", + "dxf", + "e2d", + "egt", + "eps", + "fs", + "gbr", + "odg", + "svg", + "stl", + "vrml", + "x3d", + "sxd", + "v2d", + "vnd", + "wmf", + "emf", + "art", + "xar", + "png", + "webp", + "jxr", + "hdp", + "wdp", + "cur", + "ecw", + "iff", + "lbm", + "liff", + "nrrd", + "pam", + "pcx", + "pgf", + "sgi", + "rgb", + "rgba", + "bw", + "int", + "inta", + "sid", + "ras", + "sun", + "tga", + } + + return ext +} + +/** +* @description:get a image's ext +* @param {string} path "file path" +* @return {string} ext "file ext" +* @return {error} err "error info" + */ +func GetImageExt(p string) (string, error) { + file, err := os.Open(p) + if err != nil { + return "", err + } + + buff := make([]byte, 512) + + _, err = file.Read(buff) + + if err != nil { + return "", err + } + + filetype := http.DetectContentType(buff) + + ext := ImageExtArray() + + for i := 0; i < len(ext); i++ { + if strings.Contains(ext[i], filetype[6:]) { + return ext[i], nil + } + } + + return "", errors.New("invalid image type") +} + +func GetImageExtByName(p string) (string, error) { + + extArr := ImageExtArray() + ext := filepath.Ext(p) + for i := 0; i < len(extArr); i++ { + if strings.Contains(ext, extArr[i]) { + return extArr[i], nil + } + } + return "", errors.New("invalid image type") +} diff --git a/pkg/utils/random/random.go b/pkg/utils/random/random.go new file mode 100644 index 0000000..6909cad --- /dev/null +++ b/pkg/utils/random/random.go @@ -0,0 +1,24 @@ +package random + +import ( + "math/rand" + "time" +) + +func RandomString(n int, onlyLetter bool) string { + + var letters []rune + + if onlyLetter { + letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") + } else { + letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") + } + + b := make([]rune, n) + rand.Seed(time.Now().UnixNano()) + for i := range b { + b[i] = letters[rand.Intn(len(letters))] + } + return string(b) +} diff --git a/route/route.go b/route/route.go new file mode 100644 index 0000000..a58ed6c --- /dev/null +++ b/route/route.go @@ -0,0 +1,64 @@ +package route + +import ( + "os" + + "github.com/IceWhaleTech/CasaOS-Common/utils/jwt" + "github.com/IceWhaleTech/CasaOS-UserService/middleware" + v1 "github.com/IceWhaleTech/CasaOS-UserService/route/v1" + "github.com/gin-contrib/gzip" + "github.com/gin-gonic/gin" +) + +func InitRouter() *gin.Engine { + r := gin.Default() + r.Use(middleware.Cors()) + r.Use(middleware.WriteLog()) + r.Use(gzip.Gzip(gzip.DefaultCompression)) + + // check if environment variable is set + if ginMode, success := os.LookupEnv("GIN_MODE"); success { + gin.SetMode(ginMode) + } else { + gin.SetMode(gin.ReleaseMode) + } + + r.POST("/v1/users/register", v1.PostUserRegister) + r.POST("/v1/users/login", v1.PostUserLogin) + r.GET("/v1/users/name", v1.GetUserAllUsername) // all/name + r.POST("/v1/users/refresh", v1.PostUserRefreshToken) + // No short-term modifications + r.GET("/v1/users/image", v1.GetUserImage) + + r.GET("/v1/users/status", v1.GetUserStatus) // init/check + + v1Group := r.Group("/v1") + + v1Group.Use(jwt.JWT()) + { + v1UsersGroup := v1Group.Group("/users") + v1UsersGroup.Use() + { + v1UsersGroup.GET("/current", v1.GetUserInfo) + v1UsersGroup.PUT("/current", v1.PutUserInfo) + v1UsersGroup.PUT("/current/password", v1.PutUserPassword) + + v1UsersGroup.GET("/current/custom/:key", v1.GetUserCustomConf) + v1UsersGroup.POST("/current/custom/:key", v1.PostUserCustomConf) + v1UsersGroup.DELETE("/current/custom/:key", v1.DeleteUserCustomConf) + + v1UsersGroup.POST("/current/image/:key", v1.PostUserUploadImage) + v1UsersGroup.PUT("/current/image/:key", v1.PutUserImage) + // v1UserGroup.POST("/file/image/:key", v1.PostUserFileImage) + v1UsersGroup.DELETE("/current/image", v1.DeleteUserImage) + + // v1UserGroup.PUT("/avatar", v1.PutUserAvatar) + // v1UserGroup.GET("/avatar", v1.GetUserAvatar) + v1UsersGroup.DELETE("/:id", v1.DeleteUser) + v1UsersGroup.GET("/:username", v1.GetUserInfoByUsername) + v1UsersGroup.DELETE("", v1.DeleteUserAll) + } + } + + return r +} diff --git a/route/v1/user.go b/route/v1/user.go new file mode 100644 index 0000000..1ab5077 --- /dev/null +++ b/route/v1/user.go @@ -0,0 +1,679 @@ +package v1 + +import ( + json2 "encoding/json" + "io/ioutil" + "net/http" + url2 "net/url" + "os" + "path" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/IceWhaleTech/CasaOS-Common/utils/common_err" + "github.com/IceWhaleTech/CasaOS-Common/utils/jwt" + "github.com/IceWhaleTech/CasaOS-UserService/model" + "github.com/IceWhaleTech/CasaOS-UserService/model/system_model" + "github.com/IceWhaleTech/CasaOS-UserService/pkg/config" + "github.com/IceWhaleTech/CasaOS-UserService/pkg/utils/encryption" + "github.com/IceWhaleTech/CasaOS-UserService/pkg/utils/file" + model2 "github.com/IceWhaleTech/CasaOS-UserService/service/model" + uuid "github.com/satori/go.uuid" + "github.com/tidwall/gjson" + + "github.com/IceWhaleTech/CasaOS-UserService/service" + "github.com/gin-gonic/gin" +) + +// @Summary register user +// @Router /user/register/ [post] +func PostUserRegister(c *gin.Context) { + json := make(map[string]string) + c.ShouldBind(&json) + + username := json["username"] + pwd := json["password"] + key := json["key"] + if _, ok := service.UserRegisterHash[key]; !ok { + c.JSON(common_err.CLIENT_ERROR, + model.Result{Success: common_err.KEY_NOT_EXIST, Message: common_err.GetMsg(common_err.KEY_NOT_EXIST)}) + return + } + + if len(username) == 0 || len(pwd) == 0 { + c.JSON(common_err.CLIENT_ERROR, + model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)}) + return + } + if len(pwd) < 6 { + c.JSON(common_err.CLIENT_ERROR, + model.Result{Success: common_err.PWD_IS_TOO_SIMPLE, Message: common_err.GetMsg(common_err.PWD_IS_TOO_SIMPLE)}) + return + } + oldUser := service.MyService.User().GetUserInfoByUserName(username) + if oldUser.Id > 0 { + c.JSON(common_err.CLIENT_ERROR, + model.Result{Success: common_err.USER_EXIST, Message: common_err.GetMsg(common_err.USER_EXIST)}) + return + } + + user := model2.UserDBModel{} + user.Username = username + user.Password = encryption.GetMD5ByStr(pwd) + user.Role = "admin" + + user = service.MyService.User().CreateUser(user) + if user.Id == 0 { + c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR)}) + return + } + file.MkDir(config.AppInfo.UserDataPath + "/" + strconv.Itoa(user.Id)) + delete(service.UserRegisterHash, key) + c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS)}) +} + +// @Summary login +// @Produce application/json +// @Accept application/json +// @Tags user +// @Param user_name query string true "User name" +// @Param pwd query string true "password" +// @Success 200 {string} string "ok" +// @Router /user/login [post] +func PostUserLogin(c *gin.Context) { + json := make(map[string]string) + c.ShouldBind(&json) + + username := json["username"] + + password := json["password"] + // check params is empty + if len(username) == 0 || len(password) == 0 { + c.JSON(common_err.CLIENT_ERROR, + model.Result{ + Success: common_err.CLIENT_ERROR, + Message: common_err.GetMsg(common_err.INVALID_PARAMS), + }) + return + } + user := service.MyService.User().GetUserAllInfoByName(username) + if user.Id == 0 { + c.JSON(common_err.CLIENT_ERROR, + model.Result{Success: common_err.USER_NOT_EXIST, Message: common_err.GetMsg(common_err.USER_NOT_EXIST)}) + return + } + if user.Password != encryption.GetMD5ByStr(password) { + c.JSON(common_err.CLIENT_ERROR, + model.Result{Success: common_err.PWD_INVALID, Message: common_err.GetMsg(common_err.PWD_INVALID)}) + return + } + token := system_model.VerifyInformation{} + token.AccessToken = jwt.GetAccessToken(user.Username, user.Password, user.Id) + token.RefreshToken = jwt.GetRefreshToken(user.Username, user.Password, user.Id) + token.ExpiresAt = time.Now().Add(3 * time.Hour * time.Duration(1)).Unix() + data := make(map[string]interface{}, 2) + user.Password = "" + data["token"] = token + + // TODO:1 Database fields cannot be external + data["user"] = user + + c.JSON(common_err.SUCCESS, + model.Result{ + Success: common_err.SUCCESS, + Message: common_err.GetMsg(common_err.SUCCESS), + Data: data, + }) +} + +// @Summary edit user head +// @Produce application/json +// @Accept multipart/form-data +// @Tags user +// @Param file formData file true "用户头像" +// @Security ApiKeyAuth +// @Success 200 {string} string "ok" +// @Router /user/avatar [put] +func PutUserAvatar(c *gin.Context) { + id := c.GetHeader("user_id") + user := service.MyService.User().GetUserInfoById(id) + if user.Id == 0 { + c.JSON(common_err.SERVICE_ERROR, + model.Result{Success: common_err.USER_NOT_EXIST, Message: common_err.GetMsg(common_err.USER_NOT_EXIST)}) + return + } + f, err := c.FormFile("file") + if err != nil { + c.JSON(common_err.CLIENT_ERROR, + model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.CLIENT_ERROR), Data: err.Error()}) + return + } + if len(user.Avatar) > 0 { + os.RemoveAll(config.AppInfo.UserDataPath + "/" + id + "/" + user.Avatar) + } + ext := filepath.Ext(f.Filename) + avatarPath := config.AppInfo.UserDataPath + "/" + id + "/avatar" + ext + c.SaveUploadedFile(f, avatarPath) + user.Avatar = avatarPath + service.MyService.User().UpdateUser(user) + c.JSON(http.StatusOK, + model.Result{ + Success: common_err.SUCCESS, + Message: common_err.GetMsg(common_err.SUCCESS), + Data: user, + }) +} + +// @Summary edit user name +// @Produce application/json +// @Accept application/json +// @Tags user +// @Param old_name query string true "Old user name" +// @Security ApiKeyAuth +// @Success 200 {string} string "ok" +// @Router /user/name/:id [put] +func PutUserInfo(c *gin.Context) { + id := c.GetHeader("user_id") + json := model2.UserDBModel{} + c.ShouldBind(&json) + user := service.MyService.User().GetUserInfoById(id) + if user.Id == 0 { + c.JSON(common_err.SERVICE_ERROR, + model.Result{Success: common_err.USER_NOT_EXIST, Message: common_err.GetMsg(common_err.USER_NOT_EXIST)}) + return + } + if len(json.Username) > 0 { + u := service.MyService.User().GetUserInfoByUserName(json.Username) + if u.Id > 0 { + c.JSON(common_err.CLIENT_ERROR, + model.Result{Success: common_err.USER_EXIST, Message: common_err.GetMsg(common_err.USER_EXIST)}) + return + } + } + + if len(json.Email) == 0 { + json.Email = user.Email + } + if len(json.Avatar) == 0 { + json.Avatar = user.Avatar + } + if len(json.Role) == 0 { + json.Role = user.Role + } + if len(json.Description) == 0 { + json.Description = user.Description + } + if len(json.Nickname) == 0 { + json.Nickname = user.Nickname + } + service.MyService.User().UpdateUser(json) + c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: json}) +} + +// @Summary edit user password +// @Produce application/json +// @Accept application/json +// @Tags user +// @Security ApiKeyAuth +// @Success 200 {string} string "ok" +// @Router /user/password/:id [put] +func PutUserPassword(c *gin.Context) { + id := c.GetHeader("user_id") + json := make(map[string]string) + c.ShouldBind(&json) + oldPwd := json["old_password"] + pwd := json["password"] + if len(oldPwd) == 0 || len(pwd) == 0 { + c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)}) + return + } + user := service.MyService.User().GetUserAllInfoById(id) + if user.Id == 0 { + c.JSON(common_err.SERVICE_ERROR, + model.Result{Success: common_err.USER_NOT_EXIST, Message: common_err.GetMsg(common_err.USER_NOT_EXIST)}) + return + } + if user.Password != encryption.GetMD5ByStr(oldPwd) { + c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.PWD_INVALID_OLD, Message: common_err.GetMsg(common_err.PWD_INVALID_OLD)}) + return + } + user.Password = encryption.GetMD5ByStr(pwd) + service.MyService.User().UpdateUserPassword(user) + user.Password = "" + c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: user}) +} + +// @Summary edit user nick +// @Produce application/json +// @Accept application/json +// @Tags user +// @Param nick_name query string false "nick name" +// @Security ApiKeyAuth +// @Success 200 {string} string "ok" +// @Router /user/nick [put] +func PutUserNick(c *gin.Context) { + id := c.GetHeader("user_id") + json := make(map[string]string) + c.ShouldBind(&json) + Nickname := json["nick_name"] + if len(Nickname) == 0 { + c.JSON(http.StatusOK, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)}) + return + } + user := service.MyService.User().GetUserInfoById(id) + if user.Id == 0 { + c.JSON(http.StatusOK, + model.Result{Success: common_err.USER_NOT_EXIST, Message: common_err.GetMsg(common_err.USER_NOT_EXIST)}) + return + } + user.Nickname = Nickname + service.MyService.User().UpdateUser(user) + c.JSON(http.StatusOK, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: user}) +} + +// @Summary edit user description +// @Produce application/json +// @Accept multipart/form-data +// @Tags user +// @Param description formData string false "Description" +// @Security ApiKeyAuth +// @Success 200 {string} string "ok" +// @Router /user/desc [put] +func PutUserDesc(c *gin.Context) { + id := c.GetHeader("user_id") + json := make(map[string]string) + c.ShouldBind(&json) + desc := json["description"] + if len(desc) == 0 { + c.JSON(http.StatusOK, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)}) + return + } + user := service.MyService.User().GetUserInfoById(id) + if user.Id == 0 { + c.JSON(http.StatusOK, + model.Result{Success: common_err.USER_NOT_EXIST, Message: common_err.GetMsg(common_err.USER_NOT_EXIST)}) + return + } + user.Description = desc + + service.MyService.User().UpdateUser(user) + + c.JSON(http.StatusOK, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: user}) +} + +// @Summary get user info +// @Produce application/json +// @Accept application/json +// @Tags user +// @Success 200 {string} string "ok" +// @Router /user/info/:id [get] +func GetUserInfo(c *gin.Context) { + id := c.GetHeader("user_id") + user := service.MyService.User().GetUserInfoById(id) + + c.JSON(common_err.SUCCESS, + model.Result{ + Success: common_err.SUCCESS, + Message: common_err.GetMsg(common_err.SUCCESS), + Data: user, + }) +} + +/** + * @description: + * @param {*gin.Context} c + * @param {string} Username + * @return {*} + * @method: + * @router: + */ +func GetUserInfoByUsername(c *gin.Context) { + username := c.Param("username") + if len(username) == 0 { + c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)}) + return + } + user := service.MyService.User().GetUserInfoByUserName(username) + if user.Id == 0 { + c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.USER_NOT_EXIST, Message: common_err.GetMsg(common_err.USER_NOT_EXIST)}) + return + } + + c.JSON(common_err.SUCCESS, + model.Result{ + Success: common_err.SUCCESS, + Message: common_err.GetMsg(common_err.SUCCESS), + Data: user, + }) +} + +/** + * @description: get all Usernames + * @method:GET + * @router:/user/all/name + */ +func GetUserAllUsername(c *gin.Context) { + users := service.MyService.User().GetAllUserName() + names := []string{} + for _, v := range users { + names = append(names, v.Username) + } + c.JSON(common_err.SUCCESS, + model.Result{ + Success: common_err.SUCCESS, + Message: common_err.GetMsg(common_err.SUCCESS), + Data: names, + }) +} + +/** + * @description:get custom file by user + * @param {path} name string "file name" + * @method: GET + * @router: /user/custom/:key + */ +func GetUserCustomConf(c *gin.Context) { + name := c.Param("key") + if len(name) == 0 { + c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)}) + return + } + id := c.GetHeader("user_id") + + user := service.MyService.User().GetUserInfoById(id) + // user := service.MyService.User().GetUserInfoByUsername(Username) + if user.Id == 0 { + c.JSON(common_err.SERVICE_ERROR, + model.Result{Success: common_err.USER_NOT_EXIST, Message: common_err.GetMsg(common_err.USER_NOT_EXIST)}) + return + } + filePath := config.AppInfo.UserDataPath + "/" + id + "/" + name + ".json" + + data := file.ReadFullFile(filePath) + if !gjson.ValidBytes(data) { + c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: string(data)}) + return + } + c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: json2.RawMessage(string(data))}) +} + +/** + * @description:create or update custom conf by user + * @param {path} name string "file name" + * @method:POST + * @router:/user/custom/:key + */ +func PostUserCustomConf(c *gin.Context) { + name := c.Param("key") + if len(name) == 0 { + c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)}) + return + } + id := c.GetHeader("user_id") + user := service.MyService.User().GetUserInfoById(id) + if user.Id == 0 { + c.JSON(common_err.SERVICE_ERROR, + model.Result{Success: common_err.USER_NOT_EXIST, Message: common_err.GetMsg(common_err.USER_NOT_EXIST)}) + return + } + data, _ := ioutil.ReadAll(c.Request.Body) + filePath := config.AppInfo.UserDataPath + "/" + strconv.Itoa(user.Id) + + file.WriteToPath(data, filePath, name+".json") + c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: json2.RawMessage(string(data))}) +} + +/** + * @description: delete user custom config + * @param {path} key string + * @method:delete + * @router:/user/custom/:key + */ +func DeleteUserCustomConf(c *gin.Context) { + name := c.Param("key") + if len(name) == 0 { + c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)}) + return + } + id := c.GetHeader("user_id") + user := service.MyService.User().GetUserInfoById(id) + if user.Id == 0 { + c.JSON(common_err.SERVICE_ERROR, + model.Result{Success: common_err.USER_NOT_EXIST, Message: common_err.GetMsg(common_err.USER_NOT_EXIST)}) + return + } + filePath := config.AppInfo.UserDataPath + "/" + strconv.Itoa(user.Id) + "/" + name + ".json" + err := os.Remove(filePath) + if err != nil { + c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.SERVICE_ERROR, Message: common_err.GetMsg(common_err.SERVICE_ERROR)}) + return + } + c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS)}) +} + +/** + * @description: + * @param {path} id string "user id" + * @method:DELETE + * @router:/user/delete/:id + */ +func DeleteUser(c *gin.Context) { + id := c.Param("id") + service.MyService.User().DeleteUserById(id) + c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: id}) +} + +/** + * @description:update user image + * @method:POST + * @router:/user/current/image/:key + */ +func PutUserImage(c *gin.Context) { + id := c.GetHeader("user_id") + json := make(map[string]string) + c.ShouldBind(&json) + + path := json["path"] + key := c.Param("key") + if len(path) == 0 || len(key) == 0 { + c.JSON(http.StatusOK, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)}) + return + } + if !file.Exists(path) { + c.JSON(http.StatusOK, model.Result{Success: common_err.FILE_DOES_NOT_EXIST, Message: common_err.GetMsg(common_err.FILE_DOES_NOT_EXIST)}) + return + } + + _, err := file.GetImageExt(path) + if err != nil { + c.JSON(http.StatusOK, model.Result{Success: common_err.NOT_IMAGE, Message: common_err.GetMsg(common_err.NOT_IMAGE)}) + return + } + + user := service.MyService.User().GetUserInfoById(id) + if user.Id == 0 { + c.JSON(http.StatusOK, model.Result{Success: common_err.USER_NOT_EXIST, Message: common_err.GetMsg(common_err.USER_NOT_EXIST)}) + return + } + fstat, _ := os.Stat(path) + if fstat.Size() > 10<<20 { + c.JSON(http.StatusOK, model.Result{Success: common_err.IMAGE_TOO_LARGE, Message: common_err.GetMsg(common_err.IMAGE_TOO_LARGE)}) + return + } + ext := file.GetExt(path) + filePath := config.AppInfo.UserDataPath + "/" + strconv.Itoa(user.Id) + "/" + key + ext + file.CopySingleFile(path, filePath, "overwrite") + + data := make(map[string]string, 3) + data["path"] = filePath + data["file_name"] = key + ext + data["online_path"] = "/v1/users/image?path=" + filePath + c.JSON(http.StatusOK, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: data}) +} + +/** +* @description: +* @param {*gin.Context} c +* @param {file} file +* @param {string} key +* @param {string} type:avatar,background +* @return {*} +* @method: +* @router: + */ +func PostUserUploadImage(c *gin.Context) { + id := c.GetHeader("user_id") + f, err := c.FormFile("file") + key := c.Param("key") + t := c.PostForm("type") + if len(key) == 0 { + c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)}) + return + } + if err != nil { + c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.CLIENT_ERROR, Message: common_err.GetMsg(common_err.CLIENT_ERROR), Data: err.Error()}) + return + } + + _, err = file.GetImageExtByName(f.Filename) + if err != nil { + c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.NOT_IMAGE, Message: common_err.GetMsg(common_err.NOT_IMAGE)}) + return + } + ext := filepath.Ext(f.Filename) + user := service.MyService.User().GetUserInfoById(id) + + if user.Id == 0 { + c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.USER_NOT_EXIST, Message: common_err.GetMsg(common_err.USER_NOT_EXIST)}) + return + } + if t == "avatar" { + key = "avatar" + } + path := config.AppInfo.UserDataPath + "/" + strconv.Itoa(user.Id) + "/" + key + ext + + c.SaveUploadedFile(f, path) + data := make(map[string]string, 3) + data["path"] = path + data["file_name"] = key + ext + data["online_path"] = "/v1/users/image?path=" + path + c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: data}) +} + +/** + * @description: get current user's image + * @method:GET + * @router:/user/image/:id + */ +func GetUserImage(c *gin.Context) { + filePath := c.Query("path") + if len(filePath) == 0 { + c.JSON(http.StatusNotFound, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)}) + return + } + if !file.Exists(filePath) { + c.JSON(http.StatusNotFound, model.Result{Success: common_err.FILE_DOES_NOT_EXIST, Message: common_err.GetMsg(common_err.FILE_DOES_NOT_EXIST)}) + return + } + if !strings.Contains(filePath, config.AppInfo.UserDataPath) { + c.JSON(http.StatusNotFound, model.Result{Success: common_err.INSUFFICIENT_PERMISSIONS, Message: common_err.GetMsg(common_err.INSUFFICIENT_PERMISSIONS)}) + return + } + + fileTmp, _ := os.Open(filePath) + defer fileTmp.Close() + + fileName := path.Base(filePath) + + // @tiger - RESTful 规范下不应该返回文件本身内容,而是返回文件的静态URL,由前端去解析 + c.Header("Content-Disposition", "attachment; filename*=utf-8''"+url2.PathEscape(fileName)) + c.File(filePath) +} + +func DeleteUserImage(c *gin.Context) { + id := c.GetHeader("user_id") + path := c.Query("path") + if len(path) == 0 { + c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.INVALID_PARAMS, Message: common_err.GetMsg(common_err.INVALID_PARAMS)}) + return + } + user := service.MyService.User().GetUserInfoById(id) + if user.Id == 0 { + c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.USER_NOT_EXIST, Message: common_err.GetMsg(common_err.USER_NOT_EXIST)}) + return + } + if !file.Exists(path) { + c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.FILE_DOES_NOT_EXIST, Message: common_err.GetMsg(common_err.FILE_DOES_NOT_EXIST)}) + return + } + if !strings.Contains(path, config.AppInfo.UserDataPath+"/"+strconv.Itoa(user.Id)) { + c.JSON(common_err.SERVICE_ERROR, model.Result{Success: common_err.INSUFFICIENT_PERMISSIONS, Message: common_err.GetMsg(common_err.INSUFFICIENT_PERMISSIONS)}) + return + } + os.Remove(path) + c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS)}) +} + +/** + * @description: + * @param {*gin.Context} c + * @param {string} refresh_token + * @return {*} + * @method: + * @router: + */ +func PostUserRefreshToken(c *gin.Context) { + js := make(map[string]string) + c.ShouldBind(&js) + refresh := js["refresh_token"] + claims, err := jwt.ParseToken(refresh, true) + if err != nil { + c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.VERIFICATION_FAILURE, Message: common_err.GetMsg(common_err.VERIFICATION_FAILURE), Data: err.Error()}) + return + } + if !claims.VerifyExpiresAt(time.Now(), true) || !claims.VerifyIssuer("refresh", true) { + c.JSON(common_err.CLIENT_ERROR, model.Result{Success: common_err.VERIFICATION_FAILURE, Message: common_err.GetMsg(common_err.VERIFICATION_FAILURE)}) + return + } + newToken := jwt.GetAccessToken(claims.Username, claims.Password, claims.ID) + verifyInfo := system_model.VerifyInformation{} + verifyInfo.AccessToken = newToken + verifyInfo.RefreshToken = jwt.GetRefreshToken(claims.Username, claims.Password, claims.ID) + verifyInfo.ExpiresAt = time.Now().Add(3 * time.Hour * time.Duration(1)).Unix() + + c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS), Data: verifyInfo}) +} + +func DeleteUserAll(c *gin.Context) { + service.MyService.User().DeleteAllUser() + c.JSON(common_err.SUCCESS, model.Result{Success: common_err.SUCCESS, Message: common_err.GetMsg(common_err.SUCCESS)}) +} + +// @Summary 检查是否进入引导状态 +// @Produce application/json +// @Accept application/json +// @Tags sys +// @Security ApiKeyAuth +// @Success 200 {string} string "ok" +// @Router /sys/init/check [get] +func GetUserStatus(c *gin.Context) { + data := make(map[string]interface{}, 2) + + if service.MyService.User().GetUserCount() > 0 { + data["initialized"] = true + data["key"] = "" + } else { + key := uuid.NewV4().String() + service.UserRegisterHash[key] = key + data["key"] = key + data["initialized"] = false + } + c.JSON(common_err.SUCCESS, + model.Result{ + Success: common_err.SUCCESS, + Message: common_err.GetMsg(common_err.SUCCESS), + Data: data, + }) +} diff --git a/service/model/o_user.go b/service/model/o_user.go new file mode 100644 index 0000000..34b080b --- /dev/null +++ b/service/model/o_user.go @@ -0,0 +1,30 @@ +/* + * @Author: LinkLeong link@icewhale.com + * @Date: 2022-05-13 18:15:46 + * @LastEditors: LinkLeong + * @LastEditTime: 2022-07-11 17:57:00 + * @Description: + * @Website: https://www.casaos.io + * Copyright (c) 2022 by icewhale, All Rights Reserved. + */ +package model + +import "time" + +//Soon to be removed +type UserDBModel struct { + Id int `gorm:"column:id;primary_key" json:"id"` + Username string `json:"username"` + Password string `json:"password,omitempty"` + Role string `json:"role"` + Email string `json:"email"` + Nickname string `json:"nickname"` + Avatar string `json:"avatar"` + Description string `json:"description"` + CreatedAt time.Time `gorm:"<-:create;autoCreateTime" json:"created_at,omitempty"` + UpdatedAt time.Time `gorm:"<-:create;<-:update;autoUpdateTime" json:"updated_at,omitempty"` +} + +func (p *UserDBModel) TableName() string { + return "o_users" +} diff --git a/service/service.go b/service/service.go new file mode 100644 index 0000000..8d7f9db --- /dev/null +++ b/service/service.go @@ -0,0 +1,39 @@ +package service + +import ( + gateway "github.com/IceWhaleTech/CasaOS-Gateway/common" + "gorm.io/gorm" +) + +var MyService Repository + +type Repository interface { + Gateway() gateway.ManagementService + User() UserService +} + +func NewService(db *gorm.DB, RuntimePath string) Repository { + + gatewayManagement, err := gateway.NewManagementService(RuntimePath) + if err != nil { + panic(err) + } + + return &store{ + gateway: gatewayManagement, + user: NewUserService(db), + } +} + +type store struct { + gateway gateway.ManagementService + user UserService +} + +func (c *store) Gateway() gateway.ManagementService { + return c.gateway +} + +func (c *store) User() UserService { + return c.user +} diff --git a/service/user.go b/service/user.go new file mode 100644 index 0000000..21d9148 --- /dev/null +++ b/service/user.go @@ -0,0 +1,98 @@ +/* + * @Author: LinkLeong link@icewhale.com + * @Date: 2022-03-18 11:40:55 + * @LastEditors: LinkLeong + * @LastEditTime: 2022-07-12 10:05:37 + * @Description: + * @Website: https://www.casaos.io + * Copyright (c) 2022 by icewhale, All Rights Reserved. + */ +package service + +import ( + "io" + "mime/multipart" + "os" + + "github.com/IceWhaleTech/CasaOS-UserService/service/model" + "gorm.io/gorm" +) + +type UserService interface { + UpLoadFile(file multipart.File, name string) error + CreateUser(m model.UserDBModel) model.UserDBModel + GetUserCount() (userCount int64) + UpdateUser(m model.UserDBModel) + UpdateUserPassword(m model.UserDBModel) + GetUserInfoById(id string) (m model.UserDBModel) + GetUserAllInfoById(id string) (m model.UserDBModel) + GetUserAllInfoByName(userName string) (m model.UserDBModel) + DeleteUserById(id string) + DeleteAllUser() + GetUserInfoByUserName(userName string) (m model.UserDBModel) + GetAllUserName() (list []model.UserDBModel) +} + +var UserRegisterHash = make(map[string]string) + +type userService struct { + db *gorm.DB +} + +func (u *userService) DeleteAllUser() { + u.db.Where("1=1").Delete(&model.UserDBModel{}) +} +func (u *userService) DeleteUserById(id string) { + u.db.Where("id= ?", id).Delete(&model.UserDBModel{}) +} + +func (u *userService) GetAllUserName() (list []model.UserDBModel) { + u.db.Select("username").Find(&list) + return +} +func (u *userService) CreateUser(m model.UserDBModel) model.UserDBModel { + u.db.Create(&m) + return m +} + +func (u *userService) GetUserCount() (userCount int64) { + u.db.Find(&model.UserDBModel{}).Count(&userCount) + return +} + +func (u *userService) UpdateUser(m model.UserDBModel) { + u.db.Model(&m).Omit("password").Updates(&m) +} +func (u *userService) UpdateUserPassword(m model.UserDBModel) { + u.db.Model(&m).Update("password", m.Password) +} +func (u *userService) GetUserAllInfoById(id string) (m model.UserDBModel) { + u.db.Where("id= ?", id).First(&m) + return +} +func (u *userService) GetUserAllInfoByName(userName string) (m model.UserDBModel) { + u.db.Where("username= ?", userName).First(&m) + return +} +func (u *userService) GetUserInfoById(id string) (m model.UserDBModel) { + u.db.Select("username", "id", "role", "nickname", "description", "avatar", "email").Where("id= ?", id).First(&m) + return +} + +func (u *userService) GetUserInfoByUserName(userName string) (m model.UserDBModel) { + u.db.Select("username", "id", "role", "nickname", "description", "avatar", "email").Where("username= ?", userName).First(&m) + return +} + +//上传文件 +func (c *userService) UpLoadFile(file multipart.File, url string) error { + out, _ := os.OpenFile(url, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644) + defer out.Close() + io.Copy(out, file) + return nil +} + +//获取用户Service +func NewUserService(db *gorm.DB) UserService { + return &userService{db: db} +}