mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-11-10 04:10:10 +00:00
Compare commits
2 Commits
6448a7db9e
...
d9f8785372
Author | SHA1 | Date | |
---|---|---|---|
d9f8785372 | |||
8758d74e32 |
7
api.go
7
api.go
@ -651,6 +651,11 @@ func (app *appContext) NewUser(gc *gin.Context) {
|
|||||||
respond(400, "errorNoEmail", gc)
|
respond(400, "errorNoEmail", gc)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if app.config.Section("captcha").Key("enabled").MustBool(false) && !verifyCaptcha(req.Captcha) {
|
||||||
|
app.info.Printf("%s: New user failed: Captcha Incorrect", req.Code)
|
||||||
|
respond(400, "errorCaptcha", gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
f, success := app.newUser(req, false)
|
f, success := app.newUser(req, false)
|
||||||
if !success {
|
if !success {
|
||||||
f(gc)
|
f(gc)
|
||||||
@ -1585,7 +1590,7 @@ func (app *appContext) DeleteOmbiProfile(gc *gin.Context) {
|
|||||||
|
|
||||||
// @Summary Set whether or not a user can access jfa-go. Redundant if the user is a Jellyfin admin.
|
// @Summary Set whether or not a user can access jfa-go. Redundant if the user is a Jellyfin admin.
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Param setAccountsAdminDTO body setAccountsAdminDTO true "Map of userIDs whether or not they have access."
|
// @Param setAccountsAdminDTO body setAccountsAdminDTO true "Map of userIDs to whether or not they have access."
|
||||||
// @Success 204 {object} boolResponse
|
// @Success 204 {object} boolResponse
|
||||||
// @Failure 500 {object} boolResponse
|
// @Failure 500 {object} boolResponse
|
||||||
// @Router /users/accounts-admin [post]
|
// @Router /users/accounts-admin [post]
|
||||||
|
@ -312,6 +312,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"captcha": {
|
||||||
|
"order": [],
|
||||||
|
"meta": {
|
||||||
|
"name": "Captcha",
|
||||||
|
"description": "Settings related to user creation CAPTCHAs."
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"enabled": {
|
||||||
|
"name": "Enabled",
|
||||||
|
"required": false,
|
||||||
|
"requires_restart": true,
|
||||||
|
"type": "bool",
|
||||||
|
"value": false,
|
||||||
|
"description": "Enable a CAPTCHA on the account creation form."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"password_validation": {
|
"password_validation": {
|
||||||
"order": [],
|
"order": [],
|
||||||
"meta": {
|
"meta": {
|
||||||
|
1
go.mod
1
go.mod
@ -48,6 +48,7 @@ require (
|
|||||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
|
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||||
|
github.com/steambap/captcha v1.4.1 // indirect
|
||||||
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2
|
github.com/swaggo/files v0.0.0-20210815190702-a29dd2bc99b2
|
||||||
github.com/swaggo/gin-swagger v1.3.3
|
github.com/swaggo/gin-swagger v1.3.3
|
||||||
github.com/swaggo/swag v1.7.8 // indirect
|
github.com/swaggo/swag v1.7.8 // indirect
|
||||||
|
6
go.sum
6
go.sum
@ -126,6 +126,8 @@ github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf
|
|||||||
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
|
||||||
|
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.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
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=
|
||||||
@ -239,6 +241,8 @@ github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EE
|
|||||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
|
github.com/steambap/captcha v1.4.1 h1:OmMdxLCWCqJvsFaFYwRpvMckIuvI6s8s1LsBrBw97P0=
|
||||||
|
github.com/steambap/captcha v1.4.1/go.mod h1:oC9T7IfEgnrhzjDz5Djf1H7GPffCzRMbsQfFkJmhlnk=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
@ -305,6 +309,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
|
|||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
|
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
|
||||||
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
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/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
|
golang.org/x/mod v0.5.1 h1:OJxoQ/rynoF0dcCdI7cLPktw/hR2cueqYfjm43oqK38=
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
window.matrixEnabled = {{ .matrixEnabled }};
|
window.matrixEnabled = {{ .matrixEnabled }};
|
||||||
window.matrixRequired = {{ .matrixRequired }};
|
window.matrixRequired = {{ .matrixRequired }};
|
||||||
window.matrixUserID = "{{ .matrixUser }}";
|
window.matrixUserID = "{{ .matrixUser }}";
|
||||||
|
window.captcha = {{ .captcha }};
|
||||||
</script>
|
</script>
|
||||||
{{ if .passwordReset }}
|
{{ if .passwordReset }}
|
||||||
<script src="js/pwr.js" type="module"></script>
|
<script src="js/pwr.js" type="module"></script>
|
||||||
|
@ -3,13 +3,11 @@
|
|||||||
<head>
|
<head>
|
||||||
<link rel="stylesheet" type="text/css" href="css/{{ .cssVersion }}bundle.css">
|
<link rel="stylesheet" type="text/css" href="css/{{ .cssVersion }}bundle.css">
|
||||||
{{ template "header.html" . }}
|
{{ template "header.html" . }}
|
||||||
<title>
|
{{ if .passwordReset }}
|
||||||
{{ if .passwordReset }}
|
<title>{{ .strings.passwordReset }}</title>
|
||||||
{{ .strings.passwordReset }}
|
{{ else }}
|
||||||
{{ else }}
|
<title>{{ .strings.pageTitle }}</title>
|
||||||
{{ .strings.pageTitle }}
|
{{ end }}
|
||||||
{{ end }}
|
|
||||||
</title>
|
|
||||||
</head>
|
</head>
|
||||||
<body class="max-w-full overflow-x-hidden section">
|
<body class="max-w-full overflow-x-hidden section">
|
||||||
<div id="modal-success" class="modal">
|
<div id="modal-success" class="modal">
|
||||||
@ -177,6 +175,13 @@
|
|||||||
{{ end }}
|
{{ end }}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
{{ if .captcha }}
|
||||||
|
<div class="card ~neutral @low mb-4">
|
||||||
|
<span class="label supra mb-2">CAPTCHA <span id="captcha-success" class="badge lg @low ~critical ml-2 float-right"><i class="ri-close-line"></i></span></span>
|
||||||
|
<div id="captcha-img" class="mt-2 mb-2"></div>
|
||||||
|
<input class="field ~neutral @low" id="captcha-input" class="mt-2" placeholder="CAPTCHA">
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
{{ if .contactMessage }}
|
{{ if .contactMessage }}
|
||||||
<aside class="col aside sm ~info mt-4">{{ .contactMessage }}</aside>
|
<aside class="col aside sm ~info mt-4">{{ .contactMessage }}</aside>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
@ -30,6 +30,7 @@
|
|||||||
"errorInvalidPIN": "PIN is invalid.",
|
"errorInvalidPIN": "PIN is invalid.",
|
||||||
"errorUnknown": "Unknown error.",
|
"errorUnknown": "Unknown error.",
|
||||||
"errorNoEmail": "Email required.",
|
"errorNoEmail": "Email required.",
|
||||||
|
"errorCaptcha": "Captcha incorrect.",
|
||||||
"verified": "Account verified."
|
"verified": "Account verified."
|
||||||
},
|
},
|
||||||
"validationStrings": {
|
"validationStrings": {
|
||||||
|
@ -23,6 +23,7 @@ type newUserDTO struct {
|
|||||||
DiscordContact bool `json:"discord_contact"` // Whether or not to use discord for notifications/pwrs
|
DiscordContact bool `json:"discord_contact"` // Whether or not to use discord for notifications/pwrs
|
||||||
MatrixPIN string `json:"matrix_pin" example:"A1-B2-3C"` // Matrix verification PIN (if used)
|
MatrixPIN string `json:"matrix_pin" example:"A1-B2-3C"` // Matrix verification PIN (if used)
|
||||||
MatrixContact bool `json:"matrix_contact"` // Whether or not to use matrix for notifications/pwrs
|
MatrixContact bool `json:"matrix_contact"` // Whether or not to use matrix for notifications/pwrs
|
||||||
|
Captcha string `json:"captcha"` // Captcha text (if enabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
type newUserResponse struct {
|
type newUserResponse struct {
|
||||||
@ -349,3 +350,7 @@ type LogDTO struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type setAccountsAdminDTO map[string]bool
|
type setAccountsAdminDTO map[string]bool
|
||||||
|
|
||||||
|
type genCaptchaDTO struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
}
|
||||||
|
@ -121,6 +121,11 @@ func (app *appContext) loadRoutes(router *gin.Engine) {
|
|||||||
router.POST(p+"/newUser", app.NewUser)
|
router.POST(p+"/newUser", app.NewUser)
|
||||||
router.Use(static.Serve(p+"/invite/", app.webFS))
|
router.Use(static.Serve(p+"/invite/", app.webFS))
|
||||||
router.GET(p+"/invite/:invCode", app.InviteProxy)
|
router.GET(p+"/invite/:invCode", app.InviteProxy)
|
||||||
|
if app.config.Section("captcha").Key("enabled").MustBool(false) {
|
||||||
|
router.GET(p+"/captcha/gen/:invCode", app.GenCaptcha)
|
||||||
|
router.GET(p+"/captcha/img/:invCode/:captchaID", app.GetCaptcha)
|
||||||
|
router.POST(p+"/captcha/verify/:invCode/:captchaID/:text", app.VerifyCaptcha)
|
||||||
|
}
|
||||||
if telegramEnabled {
|
if telegramEnabled {
|
||||||
router.GET(p+"/invite/:invCode/telegram/verified/:pin", app.TelegramVerifiedInvite)
|
router.GET(p+"/invite/:invCode/telegram/verified/:pin", app.TelegramVerifiedInvite)
|
||||||
}
|
}
|
||||||
|
@ -62,6 +62,8 @@ function fixHTML(infile, outfile) {
|
|||||||
let s = templateStrings[i].replace(/\\/g, '');
|
let s = templateStrings[i].replace(/\\/g, '');
|
||||||
out = out.replaceAll("<!--" + s.slice(3).slice(0, -3) + "-->", s);
|
out = out.replaceAll("<!--" + s.slice(3).slice(0, -3) + "-->", s);
|
||||||
}
|
}
|
||||||
|
out = out.replaceAll("<!--", "{{");
|
||||||
|
out = out.replaceAll("-->", "}}");
|
||||||
fs.writeFileSync(outfile, out);
|
fs.writeFileSync(outfile, out);
|
||||||
console.log(infile, outfile);
|
console.log(infile, outfile);
|
||||||
};
|
};
|
||||||
|
12
storage.go
12
storage.go
@ -12,6 +12,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hrfee/mediabrowser"
|
"github.com/hrfee/mediabrowser"
|
||||||
|
"github.com/steambap/captcha"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Storage struct {
|
type Storage struct {
|
||||||
@ -102,11 +103,12 @@ type Invite struct {
|
|||||||
UserMinutes int `json:"user-minutes,omitempty"`
|
UserMinutes int `json:"user-minutes,omitempty"`
|
||||||
SendTo string `json:"email"`
|
SendTo string `json:"email"`
|
||||||
// Used to be stored as formatted time, now as Unix.
|
// Used to be stored as formatted time, now as Unix.
|
||||||
UsedBy [][]string `json:"used-by"`
|
UsedBy [][]string `json:"used-by"`
|
||||||
Notify map[string]map[string]bool `json:"notify"`
|
Notify map[string]map[string]bool `json:"notify"`
|
||||||
Profile string `json:"profile"`
|
Profile string `json:"profile"`
|
||||||
Label string `json:"label,omitempty"`
|
Label string `json:"label,omitempty"`
|
||||||
Keys []string `json:"keys,omitempty"`
|
Keys []string `json:"keys,omitempty"`
|
||||||
|
Captchas map[string]*captcha.Data // Map of Captcha IDs to answers
|
||||||
}
|
}
|
||||||
|
|
||||||
type Lang struct {
|
type Lang struct {
|
||||||
|
46
ts/form.ts
46
ts/form.ts
@ -30,6 +30,7 @@ interface formWindow extends Window {
|
|||||||
userExpiryMinutes: number;
|
userExpiryMinutes: number;
|
||||||
userExpiryMessage: string;
|
userExpiryMessage: string;
|
||||||
emailRequired: boolean;
|
emailRequired: boolean;
|
||||||
|
captcha: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
loadLangSelector("form");
|
loadLangSelector("form");
|
||||||
@ -262,10 +263,52 @@ interface sendDTO {
|
|||||||
discord_contact?: boolean;
|
discord_contact?: boolean;
|
||||||
matrix_pin?: string;
|
matrix_pin?: string;
|
||||||
matrix_contact?: boolean;
|
matrix_contact?: boolean;
|
||||||
|
captcha?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let captchaVerified = false;
|
||||||
|
let captchaID = "";
|
||||||
|
let captchaInput = document.getElementById("captcha-input") as HTMLInputElement;
|
||||||
|
|
||||||
|
if (window.captcha) {
|
||||||
|
_get("/captcha/gen/"+window.code, null, (req: XMLHttpRequest) => {
|
||||||
|
if (req.readyState == 4) {
|
||||||
|
if (req.status == 200) {
|
||||||
|
captchaID = req.response["id"];
|
||||||
|
document.getElementById("captcha-img").innerHTML = `
|
||||||
|
<img class="w-100" src="${window.location.toString().substring(0, window.location.toString().lastIndexOf("/invite"))}/captcha/img/${window.code}/${captchaID}"></img>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const input = document.querySelector("input[type=submit]") as HTMLInputElement;
|
||||||
|
const checkbox = document.getElementById("captcha-success") as HTMLSpanElement;
|
||||||
|
captchaInput.onkeyup = () => _post("/captcha/verify/" + window.code + "/" + captchaID + "/" + captchaInput.value, null, (req: XMLHttpRequest) => {
|
||||||
|
if (req.readyState == 4) {
|
||||||
|
if (req.status == 204) {
|
||||||
|
input.disabled = false;
|
||||||
|
input.nextElementSibling.removeAttribute("disabled");
|
||||||
|
checkbox.innerHTML = `<i class="ri-check-line"></i>`;
|
||||||
|
checkbox.classList.add("~positive");
|
||||||
|
checkbox.classList.remove("~critical");
|
||||||
|
} else {
|
||||||
|
input.disabled = true;
|
||||||
|
input.nextElementSibling.setAttribute("disabled", "true");
|
||||||
|
checkbox.innerHTML = `<i class="ri-close-line"></i>`;
|
||||||
|
checkbox.classList.add("~critical");
|
||||||
|
checkbox.classList.remove("~positive");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, );
|
||||||
|
input.disabled = true;
|
||||||
|
input.nextElementSibling.setAttribute("disabled", "true");
|
||||||
}
|
}
|
||||||
|
|
||||||
const create = (event: SubmitEvent) => {
|
const create = (event: SubmitEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
if (window.captcha && !captchaVerified) {
|
||||||
|
|
||||||
|
}
|
||||||
toggleLoader(submitSpan);
|
toggleLoader(submitSpan);
|
||||||
let send: sendDTO = {
|
let send: sendDTO = {
|
||||||
code: window.code,
|
code: window.code,
|
||||||
@ -294,6 +337,9 @@ const create = (event: SubmitEvent) => {
|
|||||||
send.matrix_contact = true;
|
send.matrix_contact = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (window.captcha) {
|
||||||
|
send.captcha = captchaInput.value;
|
||||||
|
}
|
||||||
_post("/newUser", send, (req: XMLHttpRequest) => {
|
_post("/newUser", send, (req: XMLHttpRequest) => {
|
||||||
if (req.readyState == 4) {
|
if (req.readyState == 4) {
|
||||||
let vals = req.response as respDTO;
|
let vals = req.response as respDTO;
|
||||||
|
116
views.go
116
views.go
@ -10,6 +10,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/golang-jwt/jwt"
|
"github.com/golang-jwt/jwt"
|
||||||
"github.com/hrfee/mediabrowser"
|
"github.com/hrfee/mediabrowser"
|
||||||
|
"github.com/steambap/captcha"
|
||||||
)
|
)
|
||||||
|
|
||||||
var cssVersion string
|
var cssVersion string
|
||||||
@ -253,6 +254,120 @@ func (app *appContext) ResetPassword(gc *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @Summary returns the captcha image corresponding to the given ID.
|
||||||
|
// @Param code path string true "invite code"
|
||||||
|
// @Param captchaID path string true "captcha ID"
|
||||||
|
// @Tags Other
|
||||||
|
// @Router /captcha/img/{code}/{captchaID} [get]
|
||||||
|
func (app *appContext) GetCaptcha(gc *gin.Context) {
|
||||||
|
code := gc.Param("invCode")
|
||||||
|
captchaID := gc.Param("captchaID")
|
||||||
|
inv, ok := app.storage.invites[code]
|
||||||
|
if !ok {
|
||||||
|
gcHTML(gc, 404, "invalidCode.html", gin.H{
|
||||||
|
"cssClass": app.cssClass,
|
||||||
|
"cssVersion": cssVersion,
|
||||||
|
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
var capt *captcha.Data
|
||||||
|
if inv.Captchas != nil {
|
||||||
|
capt = inv.Captchas[captchaID]
|
||||||
|
}
|
||||||
|
if capt == nil {
|
||||||
|
respondBool(400, false, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := capt.WriteImage(gc.Writer); err != nil {
|
||||||
|
app.err.Printf("Failed to write CAPTCHA image: %v", err)
|
||||||
|
respondBool(500, false, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gc.Status(200)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Summary Generates a new captcha and returns it's ID. This can then be included in a request to /captcha/img/{id} to get an image.
|
||||||
|
// @Produce json
|
||||||
|
// @Param code path string true "invite code"
|
||||||
|
// @Success 200 {object} genCaptchaDTO
|
||||||
|
// @Router /captcha/gen/{code} [get]
|
||||||
|
// @Security Bearer
|
||||||
|
// @tags Users
|
||||||
|
func (app *appContext) GenCaptcha(gc *gin.Context) {
|
||||||
|
code := gc.Param("invCode")
|
||||||
|
inv, ok := app.storage.invites[code]
|
||||||
|
if !ok {
|
||||||
|
gcHTML(gc, 404, "invalidCode.html", gin.H{
|
||||||
|
"cssClass": app.cssClass,
|
||||||
|
"cssVersion": cssVersion,
|
||||||
|
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
capt, err := captcha.New(300, 100)
|
||||||
|
if err != nil {
|
||||||
|
app.err.Printf("Failed to generate captcha: %v", err)
|
||||||
|
respondBool(500, false, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if inv.Captchas == nil {
|
||||||
|
inv.Captchas = map[string]*captcha.Data{}
|
||||||
|
}
|
||||||
|
captchaID := genAuthToken()
|
||||||
|
inv.Captchas[captchaID] = capt
|
||||||
|
app.storage.invites[code] = inv
|
||||||
|
gc.JSON(200, genCaptchaDTO{captchaID})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (app *appContext) verifyCaptcha(code, id, text string) bool {
|
||||||
|
inv, ok := app.storage.invites[code]
|
||||||
|
if !ok || inv.Captchas == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c, ok := inv.Captchas[id]
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return strings.ToLower(c.Text) == strings.ToLower(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Summary returns 204 if the given Captcha contents is correct for the corresponding captcha ID and invite code.
|
||||||
|
// @Param code path string true "invite code"
|
||||||
|
// @Param captchaID path string true "captcha ID"
|
||||||
|
// @Param text path string true "Captcha text"
|
||||||
|
// @Success 204
|
||||||
|
// @Tags Other
|
||||||
|
// @Router /captcha/verify/{code}/{captchaID}/{text} [get]
|
||||||
|
func (app *appContext) VerifyCaptcha(gc *gin.Context) {
|
||||||
|
code := gc.Param("invCode")
|
||||||
|
captchaID := gc.Param("captchaID")
|
||||||
|
text := gc.Param("text")
|
||||||
|
inv, ok := app.storage.invites[code]
|
||||||
|
if !ok {
|
||||||
|
gcHTML(gc, 404, "invalidCode.html", gin.H{
|
||||||
|
"cssClass": app.cssClass,
|
||||||
|
"cssVersion": cssVersion,
|
||||||
|
"contactMessage": app.config.Section("ui").Key("contact_message").String(),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var capt *captcha.Data
|
||||||
|
if inv.Captchas != nil {
|
||||||
|
capt = inv.Captchas[captchaID]
|
||||||
|
}
|
||||||
|
if capt == nil {
|
||||||
|
respondBool(400, false, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if strings.ToLower(capt.Text) != strings.ToLower(text) {
|
||||||
|
respondBool(400, false, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
respondBool(204, true, gc)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (app *appContext) InviteProxy(gc *gin.Context) {
|
func (app *appContext) InviteProxy(gc *gin.Context) {
|
||||||
app.pushResources(gc, false)
|
app.pushResources(gc, false)
|
||||||
code := gc.Param("invCode")
|
code := gc.Param("invCode")
|
||||||
@ -370,6 +485,7 @@ func (app *appContext) InviteProxy(gc *gin.Context) {
|
|||||||
"discordEnabled": discord,
|
"discordEnabled": discord,
|
||||||
"matrixEnabled": matrix,
|
"matrixEnabled": matrix,
|
||||||
"emailRequired": app.config.Section("email").Key("required").MustBool(false),
|
"emailRequired": app.config.Section("email").Key("required").MustBool(false),
|
||||||
|
"captcha": app.config.Section("captcha").Key("enabled").MustBool(false),
|
||||||
}
|
}
|
||||||
if telegram {
|
if telegram {
|
||||||
data["telegramPIN"] = app.telegram.NewAuthToken()
|
data["telegramPIN"] = app.telegram.NewAuthToken()
|
||||||
|
Loading…
Reference in New Issue
Block a user