feat(be/fe): enable captcha (#69)
* feat(ui): enable captcha * feat(server): enable captcha * fix(ui): fix login pane layout * fix(config): remove unused config and files * fix(be/fe): clean up code * chore(fe/be): clean up code
This commit is contained in:
parent
021e5090be
commit
1fcb2223a0
22 changed files with 262 additions and 82 deletions
|
@ -12,6 +12,9 @@ server:
|
||||||
writerTimeout: 86400000 # 1 day
|
writerTimeout: 86400000 # 1 day
|
||||||
maxHeaderBytes: 512
|
maxHeaderBytes: 512
|
||||||
publicPath: "public"
|
publicPath: "public"
|
||||||
|
captchaWidth: 256
|
||||||
|
captchaHeight: 60
|
||||||
|
captchaEnabled: true
|
||||||
users:
|
users:
|
||||||
enableAuth: true
|
enableAuth: true
|
||||||
defaultAdmin: ""
|
defaultAdmin: ""
|
||||||
|
|
|
@ -12,6 +12,9 @@ server:
|
||||||
writerTimeout: 86400000 # 1 day
|
writerTimeout: 86400000 # 1 day
|
||||||
maxHeaderBytes: 512
|
maxHeaderBytes: 512
|
||||||
publicPath: "/quickshare/public"
|
publicPath: "/quickshare/public"
|
||||||
|
captchaWidth: 256
|
||||||
|
captchaHeight: 60
|
||||||
|
captchaEnabled: true
|
||||||
users:
|
users:
|
||||||
enableAuth: true
|
enableAuth: true
|
||||||
defaultAdmin: ""
|
defaultAdmin: ""
|
||||||
|
@ -21,4 +24,3 @@ users:
|
||||||
cookieHttpOnly: true
|
cookieHttpOnly: true
|
||||||
minUserNameLen: 4
|
minUserNameLen: 4
|
||||||
minPwdLen: 6
|
minPwdLen: 6
|
||||||
|
|
||||||
|
|
4
go.mod
4
go.mod
|
@ -3,10 +3,13 @@ module github.com/ihexxa/quickshare
|
||||||
go 1.13
|
go 1.13
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/afocus/captcha v0.0.0-20191010092841-4bd1f21c8868
|
||||||
github.com/boltdb/bolt v1.3.1
|
github.com/boltdb/bolt v1.3.1
|
||||||
|
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f
|
||||||
github.com/elazarl/goproxy v0.0.0-20201021153353-00ad82a08272 // indirect
|
github.com/elazarl/goproxy v0.0.0-20201021153353-00ad82a08272 // indirect
|
||||||
github.com/gin-contrib/static v0.0.0-20200916080430-d45d9a37d28e
|
github.com/gin-contrib/static v0.0.0-20200916080430-d45d9a37d28e
|
||||||
github.com/gin-gonic/gin v1.6.3
|
github.com/gin-gonic/gin v1.6.3
|
||||||
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
|
||||||
github.com/ihexxa/gocfg v0.0.0-20201206115732-ab537e3b1086
|
github.com/ihexxa/gocfg v0.0.0-20201206115732-ab537e3b1086
|
||||||
github.com/ihexxa/multipart v0.0.0-20201207132919-72f6e0e58b25
|
github.com/ihexxa/multipart v0.0.0-20201207132919-72f6e0e58b25
|
||||||
github.com/jessevdk/go-flags v1.4.0
|
github.com/jessevdk/go-flags v1.4.0
|
||||||
|
@ -18,6 +21,7 @@ require (
|
||||||
github.com/smartystreets/goconvey v1.6.4 // indirect
|
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||||
go.uber.org/zap v1.16.0
|
go.uber.org/zap v1.16.0
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
|
||||||
|
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d // indirect
|
||||||
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb // indirect
|
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb // indirect
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||||
moul.io/http2curl v1.0.0 // indirect
|
moul.io/http2curl v1.0.0 // indirect
|
||||||
|
|
9
go.sum
9
go.sum
|
@ -1,10 +1,14 @@
|
||||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/afocus/captcha v0.0.0-20191010092841-4bd1f21c8868 h1:uFrPOl1VBt/Abfl2z+A/DFc+AwmFLxEHR1+Yq6cXvww=
|
||||||
|
github.com/afocus/captcha v0.0.0-20191010092841-4bd1f21c8868/go.mod h1:srphKZ1i+yGXxl/LpBS7ZIECTjCTPzZzAMtJWoG3sLo=
|
||||||
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
|
github.com/boltdb/bolt v1.3.1 h1:JQmyP4ZBrce+ZQu0dY660FMfatumYDLun9hBCUVIkF4=
|
||||||
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f h1:q/DpyjJjZs94bziQ7YkBmIlpqbVP7yw179rnzoNVX1M=
|
||||||
|
github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
|
||||||
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
|
||||||
github.com/elazarl/goproxy v0.0.0-20201021153353-00ad82a08272 h1:Am81SElhR3XCQBunTisljzNkNese2T1FiV8jP79+dqg=
|
github.com/elazarl/goproxy v0.0.0-20201021153353-00ad82a08272 h1:Am81SElhR3XCQBunTisljzNkNese2T1FiV8jP79+dqg=
|
||||||
github.com/elazarl/goproxy v0.0.0-20201021153353-00ad82a08272/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
github.com/elazarl/goproxy v0.0.0-20201021153353-00ad82a08272/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||||
|
@ -27,6 +31,8 @@ github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD87
|
||||||
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
|
||||||
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
|
github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY=
|
||||||
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
|
||||||
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
|
||||||
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
|
||||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
|
||||||
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
|
||||||
|
@ -105,6 +111,8 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI=
|
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI=
|
||||||
|
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs=
|
||||||
|
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||||
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
|
||||||
|
@ -124,6 +132,7 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
|
|
@ -652,3 +652,10 @@ div.hr {
|
||||||
.txt-cap {
|
.txt-cap {
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.captcha {
|
||||||
|
width: 14rem;
|
||||||
|
height: 3rem;
|
||||||
|
border: solid 1px #95a5a6;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
|
@ -48,7 +48,7 @@ export interface ListUploadingsResp {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IUsersClient {
|
export interface IUsersClient {
|
||||||
login: (user: string, pwd: string) => Promise<Response>;
|
login: (user: string, pwd: string, captchaId: string, captchaInput:string) => Promise<Response>;
|
||||||
logout: () => Promise<Response>;
|
logout: () => Promise<Response>;
|
||||||
isAuthed: () => Promise<Response>;
|
isAuthed: () => Promise<Response>;
|
||||||
self: () => Promise<Response>;
|
self: () => Promise<Response>;
|
||||||
|
@ -60,6 +60,7 @@ export interface IUsersClient {
|
||||||
addRole: (role: string) => Promise<Response>;
|
addRole: (role: string) => Promise<Response>;
|
||||||
delRole: (role: string) => Promise<Response>;
|
delRole: (role: string) => Promise<Response>;
|
||||||
listRoles: () => Promise<Response>;
|
listRoles: () => Promise<Response>;
|
||||||
|
getCaptchaID: () => Promise<Response>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IFilesClient {
|
export interface IFilesClient {
|
||||||
|
|
|
@ -5,13 +5,15 @@ export class UsersClient extends BaseClient {
|
||||||
super(url);
|
super(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
login = (user: string, pwd: string): Promise<Response> => {
|
login = (user: string, pwd: string, captchaId: string, captchaInput:string): Promise<Response> => {
|
||||||
return this.do({
|
return this.do({
|
||||||
method: "post",
|
method: "post",
|
||||||
url: `${this.url}/v1/users/login`,
|
url: `${this.url}/v1/users/login`,
|
||||||
data: {
|
data: {
|
||||||
user,
|
user,
|
||||||
pwd,
|
pwd,
|
||||||
|
captchaId,
|
||||||
|
captchaInput,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -114,4 +116,12 @@ export class UsersClient extends BaseClient {
|
||||||
params: {},
|
params: {},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
getCaptchaID = (): Promise<Response> => {
|
||||||
|
return this.do({
|
||||||
|
method: "get",
|
||||||
|
url: `${this.url}/v1/captchas/`,
|
||||||
|
params: {},
|
||||||
|
});
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ export class MockUsersClient {
|
||||||
private delRoleMockResp: Promise<Response>;
|
private delRoleMockResp: Promise<Response>;
|
||||||
private listRolesMockResp: Promise<Response>;
|
private listRolesMockResp: Promise<Response>;
|
||||||
private selfMockResp: Promise<Response>;
|
private selfMockResp: Promise<Response>;
|
||||||
|
private getCaptchaIDMockResp: Promise<Response>;
|
||||||
|
|
||||||
constructor(url: string) {
|
constructor(url: string) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
|
@ -22,86 +23,93 @@ export class MockUsersClient {
|
||||||
|
|
||||||
loginMock = (resp: Promise<Response>) => {
|
loginMock = (resp: Promise<Response>) => {
|
||||||
this.loginMockResp = resp;
|
this.loginMockResp = resp;
|
||||||
}
|
};
|
||||||
logoutMock = (resp: Promise<Response>) => {
|
logoutMock = (resp: Promise<Response>) => {
|
||||||
this.logoutMockResp = resp;
|
this.logoutMockResp = resp;
|
||||||
}
|
};
|
||||||
isAuthedMock = (resp: Promise<Response>) => {
|
isAuthedMock = (resp: Promise<Response>) => {
|
||||||
this.isAuthedMockResp = resp;
|
this.isAuthedMockResp = resp;
|
||||||
}
|
};
|
||||||
setPwdMock = (resp: Promise<Response>) => {
|
setPwdMock = (resp: Promise<Response>) => {
|
||||||
this.setPwdMockResp = resp;
|
this.setPwdMockResp = resp;
|
||||||
}
|
};
|
||||||
forceSetPwdMock = (resp: Promise<Response>) => {
|
forceSetPwdMock = (resp: Promise<Response>) => {
|
||||||
this.forceSetPwdMockResp = resp;
|
this.forceSetPwdMockResp = resp;
|
||||||
}
|
};
|
||||||
addUserMock = (resp: Promise<Response>) => {
|
addUserMock = (resp: Promise<Response>) => {
|
||||||
this.addUserMockResp = resp;
|
this.addUserMockResp = resp;
|
||||||
}
|
};
|
||||||
delUserMock = (resp: Promise<Response>) => {
|
delUserMock = (resp: Promise<Response>) => {
|
||||||
this.delUserMockResp = resp;
|
this.delUserMockResp = resp;
|
||||||
}
|
};
|
||||||
listUsersMock = (resp: Promise<Response>) => {
|
listUsersMock = (resp: Promise<Response>) => {
|
||||||
this.listUsersMockResp = resp;
|
this.listUsersMockResp = resp;
|
||||||
}
|
};
|
||||||
addRoleMock = (resp: Promise<Response>) => {
|
addRoleMock = (resp: Promise<Response>) => {
|
||||||
this.addRoleMockResp = resp;
|
this.addRoleMockResp = resp;
|
||||||
}
|
};
|
||||||
delRoleMock = (resp: Promise<Response>) => {
|
delRoleMock = (resp: Promise<Response>) => {
|
||||||
this.delRoleMockResp = resp;
|
this.delRoleMockResp = resp;
|
||||||
}
|
};
|
||||||
listRolesMock = (resp: Promise<Response>) => {
|
listRolesMock = (resp: Promise<Response>) => {
|
||||||
this.listRolesMockResp = resp;
|
this.listRolesMockResp = resp;
|
||||||
}
|
};
|
||||||
slefMock = (resp: Promise<Response>) => {
|
selfMock = (resp: Promise<Response>) => {
|
||||||
this.selfMockResp = resp;
|
this.selfMockResp = resp;
|
||||||
}
|
};
|
||||||
|
getCaptchaIDMock = (resp: Promise<Response>) => {
|
||||||
|
this.getCaptchaIDMockResp = resp;
|
||||||
|
};
|
||||||
|
|
||||||
login = (user: string, pwd: string): Promise<Response> => {
|
login = (user: string, pwd: string): Promise<Response> => {
|
||||||
return this.loginMockResp;
|
return this.loginMockResp;
|
||||||
}
|
};
|
||||||
|
|
||||||
logout = (): Promise<Response> => {
|
logout = (): Promise<Response> => {
|
||||||
return this.logoutMockResp;
|
return this.logoutMockResp;
|
||||||
}
|
};
|
||||||
|
|
||||||
isAuthed = (): Promise<Response> => {
|
isAuthed = (): Promise<Response> => {
|
||||||
return this.isAuthedMockResp;
|
return this.isAuthedMockResp;
|
||||||
}
|
};
|
||||||
|
|
||||||
setPwd = (oldPwd: string, newPwd: string): Promise<Response> => {
|
setPwd = (oldPwd: string, newPwd: string): Promise<Response> => {
|
||||||
return this.setPwdMockResp;
|
return this.setPwdMockResp;
|
||||||
}
|
};
|
||||||
|
|
||||||
forceSetPwd = (userID: string, newPwd: string): Promise<Response> => {
|
forceSetPwd = (userID: string, newPwd: string): Promise<Response> => {
|
||||||
return this.forceSetPwdMockResp;
|
return this.forceSetPwdMockResp;
|
||||||
}
|
};
|
||||||
|
|
||||||
addUser = (name: string, pwd: string, role: string): Promise<Response> => {
|
addUser = (name: string, pwd: string, role: string): Promise<Response> => {
|
||||||
return this.addUserMockResp;
|
return this.addUserMockResp;
|
||||||
}
|
};
|
||||||
|
|
||||||
delUser = (userID: string): Promise<Response> => {
|
delUser = (userID: string): Promise<Response> => {
|
||||||
return this.delUserMockResp;
|
return this.delUserMockResp;
|
||||||
}
|
};
|
||||||
|
|
||||||
listUsers = (): Promise<Response> => {
|
listUsers = (): Promise<Response> => {
|
||||||
return this.listUsersMockResp;
|
return this.listUsersMockResp;
|
||||||
}
|
};
|
||||||
|
|
||||||
addRole = (role: string): Promise<Response> => {
|
addRole = (role: string): Promise<Response> => {
|
||||||
return this.addRoleMockResp;
|
return this.addRoleMockResp;
|
||||||
}
|
};
|
||||||
|
|
||||||
delRole = (role: string): Promise<Response> => {
|
delRole = (role: string): Promise<Response> => {
|
||||||
return this.delRoleMockResp;
|
return this.delRoleMockResp;
|
||||||
}
|
};
|
||||||
|
|
||||||
listRoles = (): Promise<Response> => {
|
listRoles = (): Promise<Response> => {
|
||||||
return this.listRolesMockResp;
|
return this.listRolesMockResp;
|
||||||
}
|
};
|
||||||
|
|
||||||
self = (): Promise<Response> => {
|
self = (): Promise<Response> => {
|
||||||
return this.selfMockResp;
|
return this.selfMockResp;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
getCaptchaID = (): Promise<Response> => {
|
||||||
|
return this.getCaptchaIDMockResp;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,7 @@ export function initState(): ICoreState {
|
||||||
displaying: "browser",
|
displaying: "browser",
|
||||||
authPane: {
|
authPane: {
|
||||||
authed: false,
|
authed: false,
|
||||||
|
captchaID: "",
|
||||||
},
|
},
|
||||||
browser: {
|
browser: {
|
||||||
isVertical: isVertical(),
|
isVertical: isVertical(),
|
||||||
|
@ -65,6 +66,7 @@ export function initState(): ICoreState {
|
||||||
paneNames: Set<string>(["settings", "login", "admin"]),
|
paneNames: Set<string>(["settings", "login", "admin"]),
|
||||||
login: {
|
login: {
|
||||||
authed: false,
|
authed: false,
|
||||||
|
captchaID: "",
|
||||||
},
|
},
|
||||||
admin: {
|
admin: {
|
||||||
users: Map<string, User>(),
|
users: Map<string, User>(),
|
||||||
|
@ -83,6 +85,7 @@ export function mockState(): ICoreState {
|
||||||
displaying: "browser",
|
displaying: "browser",
|
||||||
authPane: {
|
authPane: {
|
||||||
authed: false,
|
authed: false,
|
||||||
|
captchaID: "",
|
||||||
},
|
},
|
||||||
browser: {
|
browser: {
|
||||||
isVertical: false,
|
isVertical: false,
|
||||||
|
@ -98,6 +101,7 @@ export function mockState(): ICoreState {
|
||||||
paneNames: Set<string>(["settings", "login", "admin"]),
|
paneNames: Set<string>(["settings", "login", "admin"]),
|
||||||
login: {
|
login: {
|
||||||
authed: false,
|
authed: false,
|
||||||
|
captchaID: "",
|
||||||
},
|
},
|
||||||
admin: {
|
admin: {
|
||||||
users: Map<string, User>(),
|
users: Map<string, User>(),
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { Layouter } from "./layouter";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
authed: boolean;
|
authed: boolean;
|
||||||
|
captchaID: string;
|
||||||
update?: (updater: (prevState: ICoreState) => ICoreState) => void;
|
update?: (updater: (prevState: ICoreState) => ICoreState) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,8 +24,13 @@ export class Updater {
|
||||||
Updater.client = client;
|
Updater.client = client;
|
||||||
};
|
};
|
||||||
|
|
||||||
static login = async (user: string, pwd: string): Promise<boolean> => {
|
static login = async (
|
||||||
const resp = await Updater.client.login(user, pwd);
|
user: string,
|
||||||
|
pwd: string,
|
||||||
|
captchaID: string,
|
||||||
|
captchaInput: string
|
||||||
|
): Promise<boolean> => {
|
||||||
|
const resp = await Updater.client.login(user, pwd, captchaID, captchaInput);
|
||||||
Updater.setAuthed(resp.status === 200);
|
Updater.setAuthed(resp.status === 200);
|
||||||
return resp.status === 200;
|
return resp.status === 200;
|
||||||
};
|
};
|
||||||
|
@ -50,6 +56,15 @@ export class Updater {
|
||||||
Updater.props.authed = isAuthed;
|
Updater.props.authed = isAuthed;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static getCaptchaID = async (): Promise<boolean> => {
|
||||||
|
return Updater.client.getCaptchaID().then((resp) => {
|
||||||
|
if (resp.status === 200) {
|
||||||
|
Updater.props.captchaID = resp.data.id;
|
||||||
|
}
|
||||||
|
return resp.status === 200;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
static setAuthPane = (preState: ICoreState): ICoreState => {
|
static setAuthPane = (preState: ICoreState): ICoreState => {
|
||||||
preState.panel.authPane = {
|
preState.panel.authPane = {
|
||||||
...preState.panel.authPane,
|
...preState.panel.authPane,
|
||||||
|
@ -62,6 +77,7 @@ export class Updater {
|
||||||
export interface State {
|
export interface State {
|
||||||
user: string;
|
user: string;
|
||||||
pwd: string;
|
pwd: string;
|
||||||
|
captchaInput: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AuthPane extends React.Component<Props, State, {}> {
|
export class AuthPane extends React.Component<Props, State, {}> {
|
||||||
|
@ -74,6 +90,7 @@ export class AuthPane extends React.Component<Props, State, {}> {
|
||||||
this.state = {
|
this.state = {
|
||||||
user: "",
|
user: "",
|
||||||
pwd: "",
|
pwd: "",
|
||||||
|
captchaInput: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
this.initIsAuthed();
|
this.initIsAuthed();
|
||||||
|
@ -87,6 +104,10 @@ export class AuthPane extends React.Component<Props, State, {}> {
|
||||||
this.setState({ pwd: ev.target.value });
|
this.setState({ pwd: ev.target.value });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
changeCaptcha = (ev: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
this.setState({ captchaInput: ev.target.value });
|
||||||
|
};
|
||||||
|
|
||||||
initIsAuthed = () => {
|
initIsAuthed = () => {
|
||||||
Updater.initIsAuthed().then(() => {
|
Updater.initIsAuthed().then(() => {
|
||||||
this.update(Updater.setAuthPane);
|
this.update(Updater.setAuthPane);
|
||||||
|
@ -94,7 +115,12 @@ export class AuthPane extends React.Component<Props, State, {}> {
|
||||||
};
|
};
|
||||||
|
|
||||||
login = () => {
|
login = () => {
|
||||||
Updater.login(this.state.user, this.state.pwd)
|
Updater.login(
|
||||||
|
this.state.user,
|
||||||
|
this.state.pwd,
|
||||||
|
this.props.captchaID,
|
||||||
|
this.state.captchaInput
|
||||||
|
)
|
||||||
.then((ok: boolean) => {
|
.then((ok: boolean) => {
|
||||||
if (ok) {
|
if (ok) {
|
||||||
this.update(Updater.setAuthPane);
|
this.update(Updater.setAuthPane);
|
||||||
|
@ -129,33 +155,6 @@ export class AuthPane extends React.Component<Props, State, {}> {
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const elements: Array<JSX.Element> = [
|
|
||||||
<input
|
|
||||||
name="user"
|
|
||||||
type="text"
|
|
||||||
onChange={this.changeUser}
|
|
||||||
value={this.state.user}
|
|
||||||
className="black0-font margin-t-m margin-b-m"
|
|
||||||
// style={{ width: "80%" }}
|
|
||||||
placeholder="user name"
|
|
||||||
/>,
|
|
||||||
<input
|
|
||||||
name="pwd"
|
|
||||||
type="password"
|
|
||||||
onChange={this.changePwd}
|
|
||||||
value={this.state.pwd}
|
|
||||||
className="black0-font margin-t-m margin-b-m"
|
|
||||||
// style={{ width: "80%" }}
|
|
||||||
placeholder="password"
|
|
||||||
/>,
|
|
||||||
<button
|
|
||||||
onClick={this.login}
|
|
||||||
className="green0-bg white-font margin-t-m margin-b-m"
|
|
||||||
>
|
|
||||||
Log in
|
|
||||||
</button>,
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
<div
|
<div
|
||||||
|
@ -163,8 +162,52 @@ export class AuthPane extends React.Component<Props, State, {}> {
|
||||||
style={{ display: this.props.authed ? "none" : "block" }}
|
style={{ display: this.props.authed ? "none" : "block" }}
|
||||||
>
|
>
|
||||||
<div className="padding-l">
|
<div className="padding-l">
|
||||||
{/* <h5 className="black-font">Login</h5> */}
|
<div className="flex-list-container">
|
||||||
<Layouter isHorizontal={false} elements={elements} />
|
<div className="flex-list-item-l">
|
||||||
|
<input
|
||||||
|
name="user"
|
||||||
|
type="text"
|
||||||
|
onChange={this.changeUser}
|
||||||
|
value={this.state.user}
|
||||||
|
className="black0-font margin-t-m margin-b-m margin-r-m"
|
||||||
|
placeholder="user name"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
name="pwd"
|
||||||
|
type="password"
|
||||||
|
onChange={this.changePwd}
|
||||||
|
value={this.state.pwd}
|
||||||
|
className="black0-font margin-t-m margin-b-m"
|
||||||
|
placeholder="password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex-list-item-r">
|
||||||
|
<button
|
||||||
|
onClick={this.login}
|
||||||
|
className="green0-bg white-font margin-t-m margin-b-m"
|
||||||
|
>
|
||||||
|
Log in
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex-list-container">
|
||||||
|
<div className="flex-list-item-l">
|
||||||
|
<input
|
||||||
|
name="captcha"
|
||||||
|
type="text"
|
||||||
|
onChange={this.changeCaptcha}
|
||||||
|
value={this.state.captchaInput}
|
||||||
|
className="black0-font margin-t-m margin-b-m margin-r-m"
|
||||||
|
placeholder="captcha"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
src={`/v1/captchas/imgs?capid=${this.props.captchaID}`}
|
||||||
|
className="captcha"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex-list-item-l"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ import * as React from "react";
|
||||||
import { ICoreState } from "./core_state";
|
import { ICoreState } from "./core_state";
|
||||||
import { IUsersClient } from "../client";
|
import { IUsersClient } from "../client";
|
||||||
import { AuthPane, Props as LoginProps } from "./pane_login";
|
import { AuthPane, Props as LoginProps } from "./pane_login";
|
||||||
import { Layouter } from "./layouter";
|
|
||||||
import { UsersClient } from "../client/users";
|
import { UsersClient } from "../client/users";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
@ -174,6 +173,7 @@ export class PaneSettings extends React.Component<Props, State, {}> {
|
||||||
<div className="flex-list-item-r">
|
<div className="flex-list-item-r">
|
||||||
<AuthPane
|
<AuthPane
|
||||||
authed={this.props.login.authed}
|
authed={this.props.login.authed}
|
||||||
|
captchaID={this.props.login.captchaID}
|
||||||
update={this.update}
|
update={this.update}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -47,8 +47,7 @@ export class Updater {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
};
|
||||||
|
|
||||||
|
|
||||||
static addUser = async (user: User): Promise<boolean> => {
|
static addUser = async (user: User): Promise<boolean> => {
|
||||||
const resp = await Updater.client.addUser(user.name, user.pwd, user.role);
|
const resp = await Updater.client.addUser(user.name, user.pwd, user.role);
|
||||||
|
@ -156,7 +155,11 @@ export class Panes extends React.Component<Props, State, {}> {
|
||||||
<PaneSettings login={this.props.login} update={this.props.update} />
|
<PaneSettings login={this.props.login} update={this.props.update} />
|
||||||
),
|
),
|
||||||
login: (
|
login: (
|
||||||
<AuthPane authed={this.props.login.authed} update={this.props.update} />
|
<AuthPane
|
||||||
|
authed={this.props.login.authed}
|
||||||
|
captchaID={this.props.login.captchaID}
|
||||||
|
update={this.props.update}
|
||||||
|
/>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,6 @@ import * as React from "react";
|
||||||
import { ICoreState, BaseUpdater } from "./core_state";
|
import { ICoreState, BaseUpdater } from "./core_state";
|
||||||
import { Browser, Props as BrowserProps } from "./browser";
|
import { Browser, Props as BrowserProps } from "./browser";
|
||||||
import { Props as PaneLoginProps } from "./pane_login";
|
import { Props as PaneLoginProps } from "./pane_login";
|
||||||
import { Props as PaneAdminProps } from "./pane_admin";
|
|
||||||
import { Panes, Props as PanesProps, Updater as PanesUpdater } from "./panes";
|
import { Panes, Props as PanesProps, Updater as PanesUpdater } from "./panes";
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
|
@ -100,7 +99,6 @@ export class RootFrame extends React.Component<Props, State, {}> {
|
||||||
<a href="https://github.com/ihexxa/quickshare">Quickshare</a> -
|
<a href="https://github.com/ihexxa/quickshare">Quickshare</a> -
|
||||||
sharing in simple way.
|
sharing in simple way.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { ICoreState, init } from "./core_state";
|
||||||
import { RootFrame } from "./root_frame";
|
import { RootFrame } from "./root_frame";
|
||||||
import { FilesClient } from "../client/files";
|
import { FilesClient } from "../client/files";
|
||||||
import { UsersClient } from "../client/users";
|
import { UsersClient } from "../client/users";
|
||||||
|
import { Updater as LoginPaneUpdater } from "./pane_login";
|
||||||
|
|
||||||
export interface Props {}
|
export interface Props {}
|
||||||
export interface State extends ICoreState {}
|
export interface State extends ICoreState {}
|
||||||
|
@ -20,6 +21,19 @@ export class StateMgr extends React.Component<Props, State, {}> {
|
||||||
initUpdaters = (state: ICoreState) => {
|
initUpdaters = (state: ICoreState) => {
|
||||||
BrowserUpdater().init(state.panel.browser);
|
BrowserUpdater().init(state.panel.browser);
|
||||||
BrowserUpdater().setClients(new UsersClient(""), new FilesClient(""));
|
BrowserUpdater().setClients(new UsersClient(""), new FilesClient(""));
|
||||||
|
|
||||||
|
LoginPaneUpdater.init(state.panel.authPane);
|
||||||
|
LoginPaneUpdater.setClient(new UsersClient(""));
|
||||||
|
LoginPaneUpdater.getCaptchaID()
|
||||||
|
.then((ok: boolean) => {
|
||||||
|
if (!ok) {
|
||||||
|
alert("failed to get captcha id");
|
||||||
|
} else {
|
||||||
|
this.update(LoginPaneUpdater.setAuthPane);
|
||||||
|
console.log(LoginPaneUpdater)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
BrowserUpdater()
|
BrowserUpdater()
|
||||||
.setHomeItems()
|
.setHomeItems()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
|
42
src/handlers/multiusers/captcha.go
Normal file
42
src/handlers/multiusers/captcha.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package multiusers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/dchest/captcha"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
q "github.com/ihexxa/quickshare/src/handlers"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GetCaptchaIDResp struct {
|
||||||
|
CaptchaID string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *MultiUsersSvc) GetCaptchaID(c *gin.Context) {
|
||||||
|
captchaID := captcha.New()
|
||||||
|
c.JSON(200, &GetCaptchaIDResp{CaptchaID: captchaID})
|
||||||
|
}
|
||||||
|
|
||||||
|
// path: /captchas/imgs?id=xxx
|
||||||
|
func (h *MultiUsersSvc) GetCaptchaImg(c *gin.Context) {
|
||||||
|
captchaID := c.Query(q.CaptchaIDParam)
|
||||||
|
if captchaID == "" {
|
||||||
|
c.JSON(q.ErrResp(c, 400, errors.New("empty captcha ID")))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
capWidth := h.cfg.IntOr("Users.CaptchaWidth", 256)
|
||||||
|
capHeight := h.cfg.IntOr("Users.CaptchaHeight", 64)
|
||||||
|
|
||||||
|
// TODO: improve performance
|
||||||
|
buf := new(bytes.Buffer)
|
||||||
|
err := captcha.WriteImage(buf, captchaID, capWidth, capHeight)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(q.ErrResp(c, 500, err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Data(200, "image/png", buf.Bytes())
|
||||||
|
}
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/dchest/captcha"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/ihexxa/gocfg"
|
"github.com/ihexxa/gocfg"
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
@ -61,6 +62,8 @@ func NewMultiUsersSvc(cfg gocfg.ICfg, deps *depidx.Deps) (*MultiUsersSvc, error)
|
||||||
apiRuleCname(userstore.AdminRole, "DELETE", "/v1/fs/uploadings"): true,
|
apiRuleCname(userstore.AdminRole, "DELETE", "/v1/fs/uploadings"): true,
|
||||||
apiRuleCname(userstore.AdminRole, "GET", "/v1/fs/metadata"): true,
|
apiRuleCname(userstore.AdminRole, "GET", "/v1/fs/metadata"): true,
|
||||||
apiRuleCname(userstore.AdminRole, "OPTIONS", "/v1/settings/health"): true,
|
apiRuleCname(userstore.AdminRole, "OPTIONS", "/v1/settings/health"): true,
|
||||||
|
apiRuleCname(userstore.AdminRole, "GET", "/v1/captchas/"): true,
|
||||||
|
apiRuleCname(userstore.AdminRole, "GET", "/v1/captchas/imgs"): true,
|
||||||
// user rules
|
// user rules
|
||||||
apiRuleCname(userstore.UserRole, "GET", "/"): true,
|
apiRuleCname(userstore.UserRole, "GET", "/"): true,
|
||||||
apiRuleCname(userstore.UserRole, "GET", publicPath): true,
|
apiRuleCname(userstore.UserRole, "GET", publicPath): true,
|
||||||
|
@ -82,6 +85,8 @@ func NewMultiUsersSvc(cfg gocfg.ICfg, deps *depidx.Deps) (*MultiUsersSvc, error)
|
||||||
apiRuleCname(userstore.UserRole, "DELETE", "/v1/fs/uploadings"): true,
|
apiRuleCname(userstore.UserRole, "DELETE", "/v1/fs/uploadings"): true,
|
||||||
apiRuleCname(userstore.UserRole, "GET", "/v1/fs/metadata"): true,
|
apiRuleCname(userstore.UserRole, "GET", "/v1/fs/metadata"): true,
|
||||||
apiRuleCname(userstore.UserRole, "OPTIONS", "/v1/settings/health"): true,
|
apiRuleCname(userstore.UserRole, "OPTIONS", "/v1/settings/health"): true,
|
||||||
|
apiRuleCname(userstore.UserRole, "GET", "/v1/captchas/"): true,
|
||||||
|
apiRuleCname(userstore.UserRole, "GET", "/v1/captchas/imgs"): true,
|
||||||
// visitor rules
|
// visitor rules
|
||||||
apiRuleCname(userstore.VisitorRole, "GET", "/"): true,
|
apiRuleCname(userstore.VisitorRole, "GET", "/"): true,
|
||||||
apiRuleCname(userstore.VisitorRole, "GET", publicPath): true,
|
apiRuleCname(userstore.VisitorRole, "GET", publicPath): true,
|
||||||
|
@ -90,6 +95,8 @@ func NewMultiUsersSvc(cfg gocfg.ICfg, deps *depidx.Deps) (*MultiUsersSvc, error)
|
||||||
apiRuleCname(userstore.VisitorRole, "GET", "/v1/users/self"): true,
|
apiRuleCname(userstore.VisitorRole, "GET", "/v1/users/self"): true,
|
||||||
apiRuleCname(userstore.VisitorRole, "GET", "/v1/fs/files"): true,
|
apiRuleCname(userstore.VisitorRole, "GET", "/v1/fs/files"): true,
|
||||||
apiRuleCname(userstore.VisitorRole, "OPTIONS", "/v1/settings/health"): true,
|
apiRuleCname(userstore.VisitorRole, "OPTIONS", "/v1/settings/health"): true,
|
||||||
|
apiRuleCname(userstore.VisitorRole, "GET", "/v1/captchas/"): true,
|
||||||
|
apiRuleCname(userstore.VisitorRole, "GET", "/v1/captchas/imgs"): true,
|
||||||
}
|
}
|
||||||
|
|
||||||
return &MultiUsersSvc{
|
return &MultiUsersSvc{
|
||||||
|
@ -124,6 +131,8 @@ func (h *MultiUsersSvc) IsInited() bool {
|
||||||
type LoginReq struct {
|
type LoginReq struct {
|
||||||
User string `json:"user"`
|
User string `json:"user"`
|
||||||
Pwd string `json:"pwd"`
|
Pwd string `json:"pwd"`
|
||||||
|
CaptchaID string `json:"captchaId"`
|
||||||
|
CaptchaInput string `json:"captchaInput"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *MultiUsersSvc) Login(c *gin.Context) {
|
func (h *MultiUsersSvc) Login(c *gin.Context) {
|
||||||
|
@ -133,6 +142,15 @@ func (h *MultiUsersSvc) Login(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: add rate limiter for verifying
|
||||||
|
captchaEnabled := h.cfg.BoolOr("Users.CaptchaEnabled", true)
|
||||||
|
if captchaEnabled {
|
||||||
|
if !captcha.VerifyString(req.CaptchaID, req.CaptchaInput) {
|
||||||
|
c.JSON(q.ErrResp(c, 403, errors.New("login failed")))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
user, err := h.deps.Users().GetUserByName(req.User)
|
user, err := h.deps.Users().GetUserByName(req.User)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(q.ErrResp(c, 500, err))
|
c.JSON(q.ErrResp(c, 500, err))
|
||||||
|
|
|
@ -21,6 +21,7 @@ var (
|
||||||
NewPwdParam = "newpwd"
|
NewPwdParam = "newpwd"
|
||||||
RoleParam = "role"
|
RoleParam = "role"
|
||||||
ExpireParam = "expire"
|
ExpireParam = "expire"
|
||||||
|
CaptchaIDParam = "capid"
|
||||||
TokenCookie = "tk"
|
TokenCookie = "tk"
|
||||||
LastID = "lid"
|
LastID = "lid"
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,9 @@ type UsersCfg struct {
|
||||||
CookieHttpOnly bool `json:"cookieHttpOnly" yaml:"cookieHttpOnly"`
|
CookieHttpOnly bool `json:"cookieHttpOnly" yaml:"cookieHttpOnly"`
|
||||||
MinUserNameLen int `json:"minUserNameLen" yaml:"minUserNameLen"`
|
MinUserNameLen int `json:"minUserNameLen" yaml:"minUserNameLen"`
|
||||||
MinPwdLen int `json:"minPwdLen" yaml:"minPwdLen"`
|
MinPwdLen int `json:"minPwdLen" yaml:"minPwdLen"`
|
||||||
|
CaptchaWidth int `json:"captchaWidth" yaml:"captchaWidth"`
|
||||||
|
CaptchaHeight int `json:"captchaHeight" yaml:"captchaHeight"`
|
||||||
|
CaptchaEnabled bool `json:"captchaEnabled" yaml:"captchaEnabled"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Secrets struct {
|
type Secrets struct {
|
||||||
|
@ -60,6 +63,9 @@ func DefaultConfig() (string, error) {
|
||||||
CookieHttpOnly: true,
|
CookieHttpOnly: true,
|
||||||
MinUserNameLen: 4,
|
MinUserNameLen: 4,
|
||||||
MinPwdLen: 6,
|
MinPwdLen: 6,
|
||||||
|
CaptchaWidth: 256,
|
||||||
|
CaptchaHeight: 60,
|
||||||
|
CaptchaEnabled: true,
|
||||||
},
|
},
|
||||||
Secrets: &Secrets{
|
Secrets: &Secrets{
|
||||||
TokenSecret: "",
|
TokenSecret: "",
|
||||||
|
|
|
@ -189,6 +189,10 @@ func initHandlers(router *gin.Engine, cfg gocfg.ICfg, deps *depidx.Deps) (*gin.E
|
||||||
rolesAPI.DELETE("/", userHdrs.DelRole)
|
rolesAPI.DELETE("/", userHdrs.DelRole)
|
||||||
rolesAPI.GET("/list", userHdrs.ListRoles)
|
rolesAPI.GET("/list", userHdrs.ListRoles)
|
||||||
|
|
||||||
|
captchaAPI := v1.Group("/captchas")
|
||||||
|
captchaAPI.GET("/", userHdrs.GetCaptchaID)
|
||||||
|
captchaAPI.GET("/imgs", userHdrs.GetCaptchaImg)
|
||||||
|
|
||||||
filesAPI := v1.Group("/fs")
|
filesAPI := v1.Group("/fs")
|
||||||
filesAPI.POST("/files", fileHdrs.Create)
|
filesAPI.POST("/files", fileHdrs.Create)
|
||||||
filesAPI.DELETE("/files", fileHdrs.Delete)
|
filesAPI.DELETE("/files", fileHdrs.Delete)
|
||||||
|
|
|
@ -18,7 +18,8 @@ func TestConcurrency(t *testing.T) {
|
||||||
"users": {
|
"users": {
|
||||||
"enableAuth": true,
|
"enableAuth": true,
|
||||||
"minUserNameLen": 2,
|
"minUserNameLen": 2,
|
||||||
"minPwdLen": 4
|
"minPwdLen": 4,
|
||||||
|
"captchaEnabled": false
|
||||||
},
|
},
|
||||||
"server": {
|
"server": {
|
||||||
"debug": true
|
"debug": true
|
||||||
|
|
|
@ -21,7 +21,8 @@ func TestFileHandlers(t *testing.T) {
|
||||||
"users": {
|
"users": {
|
||||||
"enableAuth": true,
|
"enableAuth": true,
|
||||||
"minUserNameLen": 2,
|
"minUserNameLen": 2,
|
||||||
"minPwdLen": 4
|
"minPwdLen": 4,
|
||||||
|
"captchaEnabled": false
|
||||||
},
|
},
|
||||||
"server": {
|
"server": {
|
||||||
"debug": true
|
"debug": true
|
||||||
|
|
|
@ -19,7 +19,8 @@ func TestUsersHandlers(t *testing.T) {
|
||||||
"users": {
|
"users": {
|
||||||
"enableAuth": true,
|
"enableAuth": true,
|
||||||
"minUserNameLen": 2,
|
"minUserNameLen": 2,
|
||||||
"minPwdLen": 4
|
"minPwdLen": 4,
|
||||||
|
"captchaEnabled": false
|
||||||
},
|
},
|
||||||
"server": {
|
"server": {
|
||||||
"debug": true
|
"debug": true
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue