diff --git a/api-users.go b/api-users.go
index 138c287..f47674c 100644
--- a/api-users.go
+++ b/api-users.go
@@ -503,7 +503,7 @@ func (app *appContext) NewUser(gc *gin.Context) {
var req newUserDTO
gc.BindJSON(&req)
app.debug.Printf("%s: New user attempt", req.Code)
- if app.config.Section("captcha").Key("enabled").MustBool(false) && !app.verifyCaptcha(req.Code, req.CaptchaID, req.CaptchaText) {
+ if app.config.Section("captcha").Key("enabled").MustBool(false) && !app.verifyCaptcha(req.Code, req.CaptchaID, req.CaptchaText, false) {
app.info.Printf("%s: New user failed: Captcha Incorrect", req.Code)
respond(400, "errorCaptcha", gc)
return
diff --git a/api.go b/api.go
index 1b926e0..5c9a709 100644
--- a/api.go
+++ b/api.go
@@ -114,6 +114,7 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
var req ResetPasswordDTO
gc.BindJSON(&req)
validation := app.validator.validate(req.Password)
+ captcha := app.config.Section("captcha").Key("enabled").MustBool(false)
valid := true
for _, val := range validation {
if !val {
@@ -121,12 +122,18 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
}
}
if !valid || req.PIN == "" {
- // 200 bcs idk what i did in js
app.info.Printf("%s: Password reset failed: Invalid password", req.PIN)
gc.JSON(400, validation)
return
}
isInternal := false
+
+ if captcha && !app.verifyCaptcha(req.PIN, req.PIN, req.CaptchaText, true) {
+ app.info.Printf("%s: PWR Failed: Captcha Incorrect", req.PIN)
+ respond(400, "errorCaptcha", gc)
+ return
+ }
+
var userID, username string
if reset, ok := app.internalPWRs[req.PIN]; ok {
isInternal = true
@@ -138,6 +145,7 @@ func (app *appContext) ResetSetPassword(gc *gin.Context) {
}
userID = reset.ID
username = reset.Username
+
status, err := app.jf.ResetPasswordAdmin(userID)
if !(status == 200 || status == 204) || err != nil {
app.err.Printf("Password Reset failed (%d): %v", status, err)
diff --git a/html/form-base.html b/html/form-base.html
index ba8c318..7f46a30 100644
--- a/html/form-base.html
+++ b/html/form-base.html
@@ -34,8 +34,12 @@
{{ if .passwordReset }}
+
{{ else }}
+{{ end }}
{{ if .reCAPTCHA }}
{{ end }}
{{ end }}
-{{ end }}
diff --git a/main.go b/main.go
index 52c96c8..3128893 100644
--- a/main.go
+++ b/main.go
@@ -120,6 +120,7 @@ type appContext struct {
proxyTransport *http.Transport
proxyConfig easyproxy.ProxyConfig
internalPWRs map[string]InternalPWR
+ pwrCaptchas map[string]Captcha
ConfirmationKeys map[string]map[string]newUserDTO // Map of invite code to jwt to request
confirmationKeysLock sync.Mutex
}
diff --git a/models.go b/models.go
index ba46f7b..2e17211 100644
--- a/models.go
+++ b/models.go
@@ -332,8 +332,9 @@ type MatrixLoginDTO struct {
}
type ResetPasswordDTO struct {
- PIN string `json:"pin"`
- Password string `json:"password"`
+ PIN string `json:"pin"`
+ Password string `json:"password"`
+ CaptchaText string `json:"captcha_text"`
}
type AdminPasswordResetDTO struct {
diff --git a/ts/form.ts b/ts/form.ts
index b263481..ef4b555 100644
--- a/ts/form.ts
+++ b/ts/form.ts
@@ -4,7 +4,7 @@ import { _get, _post, toggleLoader, addLoader, removeLoader, toDateString } from
import { loadLangSelector } from "./modules/lang.js";
import { Validator, ValidatorConf, ValidatorRespDTO } from "./modules/validator.js";
import { Discord, Telegram, Matrix, ServiceConfiguration, MatrixConfiguration } from "./modules/account-linking.js";
-import { Captcha } from "./modules/captcha.js";
+import { Captcha, GreCAPTCHA } from "./modules/captcha.js";
interface formWindow extends Window {
invalidPassword: string;
@@ -173,7 +173,7 @@ if (!window.usernameEnabled) { usernameField.parentElement.remove(); usernameFie
const passwordField = document.getElementById("create-password") as HTMLInputElement;
const rePasswordField = document.getElementById("create-reenter-password") as HTMLInputElement;
-let captcha = new Captcha(window.code, window.captcha, window.reCAPTCHA);
+let captcha = new Captcha(window.code, window.captcha, window.reCAPTCHA, false);
function _baseValidator(oncomplete: (valid: boolean) => void, captchaValid: boolean): void {
if (window.emailRequired) {
@@ -203,20 +203,7 @@ function _baseValidator(oncomplete: (valid: boolean) => void, captchaValid: bool
let baseValidator = captcha.baseValidatorWrapper(_baseValidator);
-interface GreCAPTCHA {
- render: (container: HTMLDivElement, parameters: {
- sitekey?: string,
- theme?: string,
- size?: string,
- tabindex?: number,
- "callback"?: () => void,
- "expired-callback"?: () => void,
- "error-callback"?: () => void
- }) => void;
- getResponse: (opt_widget_id?: HTMLDivElement) => string;
-}
-
-declare var grecaptcha: GreCAPTCHA
+declare var grecaptcha: GreCAPTCHA;
let validatorConf: ValidatorConf = {
passwordField: passwordField,
diff --git a/ts/modules/captcha.ts b/ts/modules/captcha.ts
index ae9e5fc..97dbd8d 100644
--- a/ts/modules/captcha.ts
+++ b/ts/modules/captcha.ts
@@ -1,6 +1,7 @@
import { _get, _post } from "./common.js";
export class Captcha {
+ isPWR = false;
enabled = true;
verified = false;
captchaID = "";
@@ -27,7 +28,7 @@ export class Captcha {
};
};
- verify = (callback: () => void) => _post("/captcha/verify/" + this.code + "/" + this.captchaID + "/" + this.input.value, null, (req: XMLHttpRequest) => {
+ verify = (callback: () => void) => _post("/captcha/verify/" + this.code + "/" + this.captchaID + "/" + this.input.value + (this.isPWR ? "?pwr=true" : ""), null, (req: XMLHttpRequest) => {
if (req.readyState == 4) {
if (req.status == 204) {
this.checkbox.innerHTML = ``;
@@ -44,21 +45,37 @@ export class Captcha {
}
});
- generate = () => _get("/captcha/gen/"+this.code, null, (req: XMLHttpRequest) => {
+ generate = () => _get("/captcha/gen/"+this.code+(this.isPWR ? "?pwr=true" : ""), null, (req: XMLHttpRequest) => {
if (req.readyState == 4) {
if (req.status == 200) {
- this.captchaID = req.response["id"];
+ this.captchaID = this.isPWR ? this.code : req.response["id"];
+ // the Math.random() appearance below is used for PWRs, since they don't have a unique captchaID. The parameter is ignored by the server, but tells the browser to reload the image.
document.getElementById("captcha-img").innerHTML = `
-
+
`;
this.input.value = "";
}
}
});
- constructor(code: string, enabled: boolean, reCAPTCHA: boolean) {
+ constructor(code: string, enabled: boolean, reCAPTCHA: boolean, isPWR: boolean) {
this.code = code;
this.enabled = enabled;
this.reCAPTCHA = reCAPTCHA;
+ this.isPWR = isPWR;
}
}
+
+export interface GreCAPTCHA {
+ render: (container: HTMLDivElement, parameters: {
+ sitekey?: string,
+ theme?: string,
+ size?: string,
+ tabindex?: number,
+ "callback"?: () => void,
+ "expired-callback"?: () => void,
+ "error-callback"?: () => void
+ }) => void;
+ getResponse: (opt_widget_id?: HTMLDivElement) => string;
+}
+
diff --git a/ts/pwr.ts b/ts/pwr.ts
index 7cdaee6..b59de72 100644
--- a/ts/pwr.ts
+++ b/ts/pwr.ts
@@ -2,6 +2,7 @@ import { Modal } from "./modules/modal.js";
import { Validator, ValidatorConf } from "./modules/validator.js";
import { _post, addLoader, removeLoader } from "./modules/common.js";
import { loadLangSelector } from "./modules/lang.js";
+import { Captcha, GreCAPTCHA } from "./modules/captcha.js";
interface formWindow extends Window {
invalidPassword: string;
@@ -31,6 +32,7 @@ interface formWindow extends Window {
captcha: boolean;
reCAPTCHA: boolean;
reCAPTCHASiteKey: string;
+ pwrPIN: string;
}
loadLangSelector("pwr");
@@ -45,11 +47,26 @@ const rePasswordField = document.getElementById("create-reenter-password") as HT
window.successModal = new Modal(document.getElementById("modal-success"), true);
+function _baseValidator(oncomplete: (valid: boolean) => void, captchaValid: boolean): void {
+ if (window.captcha && !window.reCAPTCHA && !captchaValid) {
+ oncomplete(false);
+ return;
+ }
+ oncomplete(true);
+}
+
+let captcha = new Captcha(window.pwrPIN, window.captcha, window.reCAPTCHA, true);
+
+declare var grecaptcha: GreCAPTCHA;
+
+let baseValidator = captcha.baseValidatorWrapper(_baseValidator);
+
let validatorConf: ValidatorConf = {
passwordField: passwordField,
rePasswordField: rePasswordField,
submitInput: submitInput,
- submitButton: submitSpan
+ submitButton: submitSpan,
+ validatorFunc: baseValidator
};
var validator = new Validator(validatorConf);
@@ -58,6 +75,13 @@ var requirements = validator.requirements;
interface sendDTO {
pin: string;
password: string;
+ captcha_text?: string;
+}
+
+if (window.captcha && !window.reCAPTCHA) {
+ captcha.generate();
+ (document.getElementById("captcha-regen") as HTMLSpanElement).onclick = captcha.generate;
+ captcha.input.onkeyup = validator.validate;
}
form.onsubmit = (event: Event) => {
@@ -68,12 +92,31 @@ form.onsubmit = (event: Event) => {
pin: params.get("pin"),
password: passwordField.value
};
+ if (window.captcha) {
+ if (window.reCAPTCHA) {
+ send.captcha_text = grecaptcha.getResponse();
+ } else {
+ send.captcha_text = captcha.input.value;
+ }
+ }
_post("/reset", send, (req: XMLHttpRequest) => {
if (req.readyState == 4) {
removeLoader(submitSpan);
if (req.status == 400) {
- for (let type in req.response) {
- if (requirements[type]) { requirements[type].valid = req.response[type] as boolean; }
+ if (req.response["error"] as string) { // FIXME: Show captcha error
+ const old = submitSpan.textContent;
+ submitSpan.textContent = window.messages[req.response["error"]];
+ submitSpan.classList.add("~critical");
+ submitSpan.classList.remove("~urge");
+ setTimeout(() => {
+ submitSpan.classList.add("~urge");
+ submitSpan.classList.remove("~critical");
+ submitSpan.textContent = old;
+ }, 2000);
+ } else {
+ for (let type in req.response) {
+ if (requirements[type]) { requirements[type].valid = req.response[type] as boolean; }
+ }
}
return;
} else if (req.status != 200) {
diff --git a/views.go b/views.go
index 4896707..bacb51e 100644
--- a/views.go
+++ b/views.go
@@ -299,6 +299,7 @@ func (app *appContext) ResetPassword(gc *gin.Context) {
data["captcha"] = app.config.Section("captcha").Key("enabled").MustBool(false)
data["reCAPTCHA"] = app.config.Section("captcha").Key("recaptcha").MustBool(false)
data["reCAPTCHASiteKey"] = app.config.Section("captcha").Key("recaptcha_site_key").MustString("")
+ data["pwrPIN"] = pin
gcHTML(gc, http.StatusOK, "form-loader.html", data)
return
}
@@ -396,20 +397,28 @@ func (app *appContext) ResetPassword(gc *gin.Context) {
// @Router /captcha/img/{code}/{captchaID} [get]
func (app *appContext) GetCaptcha(gc *gin.Context) {
code := gc.Param("invCode")
+ isPWR := gc.Query("pwr") == "true"
captchaID := gc.Param("captchaID")
- inv, ok := app.storage.GetInvitesKey(code)
- if !ok {
- gcHTML(gc, 404, "invalidCode.html", gin.H{
- "urlBase": app.getURLBase(gc),
- "cssClass": app.cssClass,
- "cssVersion": cssVersion,
- "contactMessage": app.config.Section("ui").Key("contact_message").String(),
- })
- }
+ var inv Invite
var capt Captcha
- ok = true
- if inv.Captchas != nil {
- capt, ok = inv.Captchas[captchaID]
+ ok := true
+ if !isPWR {
+ inv, ok = app.storage.GetInvitesKey(code)
+ if !ok {
+ gcHTML(gc, 404, "invalidCode.html", gin.H{
+ "urlBase": app.getURLBase(gc),
+ "cssClass": app.cssClass,
+ "cssVersion": cssVersion,
+ "contactMessage": app.config.Section("ui").Key("contact_message").String(),
+ })
+ }
+ if inv.Captchas != nil {
+ capt, ok = inv.Captchas[captchaID]
+ } else {
+ ok = false
+ }
+ } else {
+ capt, ok = app.pwrCaptchas[code]
}
if !ok {
respondBool(400, false, gc)
@@ -428,7 +437,13 @@ func (app *appContext) GetCaptcha(gc *gin.Context) {
// @tags Users
func (app *appContext) GenCaptcha(gc *gin.Context) {
code := gc.Param("invCode")
- inv, ok := app.storage.GetInvitesKey(code)
+ isPWR := gc.Query("pwr") == "true"
+ var inv Invite
+ ok := true
+ if !isPWR {
+ inv, ok = app.storage.GetInvitesKey(code)
+ }
+
if !ok {
gcHTML(gc, 404, "invalidCode.html", gin.H{
"urlBase": app.getURLBase(gc),
@@ -443,7 +458,7 @@ func (app *appContext) GenCaptcha(gc *gin.Context) {
respondBool(500, false, gc)
return
}
- if inv.Captchas == nil {
+ if !isPWR && inv.Captchas == nil {
inv.Captchas = map[string]Captcha{}
}
captchaID := genAuthToken()
@@ -453,26 +468,43 @@ func (app *appContext) GenCaptcha(gc *gin.Context) {
respondBool(500, false, gc)
return
}
- inv.Captchas[captchaID] = Captcha{
- Answer: capt.Text,
- Image: buf.Bytes(),
- Generated: time.Now(),
+ if isPWR {
+ if app.pwrCaptchas == nil {
+ app.pwrCaptchas = map[string]Captcha{}
+ }
+ app.pwrCaptchas[code] = Captcha{
+ Answer: capt.Text,
+ Image: buf.Bytes(),
+ Generated: time.Now(),
+ }
+ } else {
+ inv.Captchas[captchaID] = Captcha{
+ Answer: capt.Text,
+ Image: buf.Bytes(),
+ Generated: time.Now(),
+ }
+ app.storage.SetInvitesKey(code, inv)
}
- app.storage.SetInvitesKey(code, inv)
gc.JSON(200, genCaptchaDTO{captchaID})
return
}
-func (app *appContext) verifyCaptcha(code, id, text string) bool {
+func (app *appContext) verifyCaptcha(code, id, text string, isPWR bool) bool {
reCAPTCHA := app.config.Section("captcha").Key("recaptcha").MustBool(false)
if !reCAPTCHA {
// internal CAPTCHA
- inv, ok := app.storage.GetInvitesKey(code)
- if !ok || inv.Captchas == nil {
- app.debug.Printf("Couldn't find invite \"%s\"", code)
- return false
+ var c Captcha
+ ok := true
+ if !isPWR {
+ inv, ok := app.storage.GetInvitesKey(code)
+ if !ok || (!isPWR && inv.Captchas == nil) {
+ app.debug.Printf("Couldn't find invite \"%s\"", code)
+ return false
+ }
+ c, ok = inv.Captchas[id]
+ } else {
+ c, ok = app.pwrCaptchas[code]
}
- c, ok := inv.Captchas[id]
if !ok {
app.debug.Printf("Couldn't find Captcha \"%s\"", id)
return false
@@ -532,21 +564,30 @@ func (app *appContext) verifyCaptcha(code, id, text string) bool {
// @Router /captcha/verify/{code}/{captchaID}/{text} [get]
func (app *appContext) VerifyCaptcha(gc *gin.Context) {
code := gc.Param("invCode")
+ isPWR := gc.Query("pwr") == "true"
captchaID := gc.Param("captchaID")
text := gc.Param("text")
- inv, ok := app.storage.GetInvitesKey(code)
- if !ok {
- gcHTML(gc, 404, "invalidCode.html", gin.H{
- "urlBase": app.getURLBase(gc),
- "cssClass": app.cssClass,
- "cssVersion": cssVersion,
- "contactMessage": app.config.Section("ui").Key("contact_message").String(),
- })
- return
- }
+ var inv Invite
var capt Captcha
- if inv.Captchas != nil {
- capt, ok = inv.Captchas[captchaID]
+ var ok bool
+ if !isPWR {
+ inv, ok = app.storage.GetInvitesKey(code)
+ if !ok {
+ gcHTML(gc, 404, "invalidCode.html", gin.H{
+ "urlBase": app.getURLBase(gc),
+ "cssClass": app.cssClass,
+ "cssVersion": cssVersion,
+ "contactMessage": app.config.Section("ui").Key("contact_message").String(),
+ })
+ return
+ }
+ if inv.Captchas != nil {
+ capt, ok = inv.Captchas[captchaID]
+ } else {
+ ok = false
+ }
+ } else {
+ capt, ok = app.pwrCaptchas[code]
}
if !ok {
respondBool(400, false, gc)