~eliasnaur/gio#80: 
Editor Option for Password Fields

I think I password field for the editor widget would be very helpful as copying and then setting the text on every input event is probably not the right way to do it.

The basic idea is that all text inputs would be replaced by * so that you can't see the password in plain text.

Status
RESOLVED FIXED
Submitter
~rexfuzzle
Assigned to
No-one
Submitted
6 months ago
Updated
a month ago
Labels
No labels applied.

~eliasnaur 6 months ago

FWIW, a patch is being worked on at https://lists.sr.ht/~eliasnaur/gio-patches/patches/9704

~rogueelement 5 months ago

Any update on this?

~aov2okrxsfn8o2 2 months ago

For what it's worth, I came up with this:

type (
	C = layout.Context
	D = layout.Dimensions
	W = layout.Widget
)
func fillArea(gtx C, sz image.Point, col color.RGBA) {
	paint.ColorOp{Color: col}.Add(gtx.Ops)
	paint.PaintOp{Rect: f32.Rectangle{
		Max: f32.Point{X: float32(sz.X), Y: float32(sz.Y)},
	}}.Add(gtx.Ops)
}

I add this password field to my page layout:

layout.Rigid(func(gtx C) D {
	gtx.Constraints.Min.X = gtx.Constraints.Max.X
	in := layout.Inset{Bottom: unit.Dp(16), Left: unit.Dp(16), Right: unit.Dp(16)}
	dims := in.Layout(gtx, func(gtx C) D {
		e := material.Editor(theme, u.pass, "Secret stuff, shhhhh")
		e.TextSize = unit.Sp(14)
		if u.pass.Text() != "" {
			in := layout.Inset{}
			in.Layout(gtx, func(gtx C) D {
				x, _ := u.pass.CaretCoords()
				fillArea(gtx, image.Point{x.Round(), 18}, rgb(0))
				return D{}
			})
		}
		return e.Layout(gtx)
	})
	return dims
}),

The result is a redacted password the Pentagon would be proud of.

~tainted-bit 2 months ago

This feature would be helpful for my use-case as well. The untested code from ~eliasnaur in patch 9704 appears to be tantalizingly close to a proper resolution. Is testing the next step to move it forward or is a different method required due to changes since the original proposal?

~eliasnaur 2 months ago

On Mon Jun 8, 2020 at 11:26 PM CEST, ~tainted-bit wrote:

This feature would be helpful for my use-case as well. The untested code from ~eliasnaur in patch 9704 appears to be tantalizingly close to a proper resolution. Is testing the next step to move it forward or is a different method required due to changes since the original proposal?

I believe the method is still viable, so what remains is testing and fixing maskReader (I don't think it properly handles partial reads, or even EOF). You may make reasonable assumptions, for example that len(b) is at least utf8.UTFMax.

-- elias

~tainted-bit a month ago

I very quickly implemented a more robust maskReader, but then ran into the caret issues that were mentioned in patch 9506. The current editBuffer implementation stores the caret position in bytes, so if the password mask rune has a different byte length than the rune it is replacing then... well, bad things happen.

A hacky workaround is to require a one-byte mask character like "*" instead of "•" and then pad the size up to the underlying rune byte length with 0x00 bytes. Surprisingly, this works flawlessly without touching the editBuffer implementation. It's not a great solution though, because it prohibits multi-byte mask characters and relies on the rendering system to ignore NUL characters. Another option is to prohibit non-ASCII characters in masked editors, but while some applications might want to do that, I wouldn't want to impose such a restriction (e.g., some applications might want to allow unicode passwords, normalize them, and then hash the UTF-8 encoding).

Because of this, I decided to take a step back and look at how other GUI frameworks handle this question ("how should unicode strings be mapped to masks?"). I tested a lot of unicode edge cases in password fields within Firefox, Chromium, GTK+3, and Qt 5 on Debian. Some of the results are quite surprising:

  • Firefox: Displays one mask for each non-zero 16-bit piece of a code point (so a CJK character gets one mask, but an emoji gets two masks). The caret position is based on code points: it is not possible to select the space between masks that belong to the same underlying code point. Deletion is based on code points. Bidirectional text applies to the masks (so the reading direction of the masks can change mid-string), and selection of bidirectional text behaves like it does for unmasked text.
  • Chromium: Visually the same as Firefox (one mask / 16-bit piece). Caret position is based on "characters" instead of code points: moving the caret left or right skips over a character (including the base code point and any combining code points). Deletion is sometimes based on code points (e.g., for letter combinations like accented Latin letters) and sometimes based on characters (e.g., for emoji combinations like emoticons with a skin tone applied). Bidirectional text is not preserved and any directionality change code points are visually masked.
  • GTK+3: Displays one mask per code point. The caret position is based on code points. Deletion is based on code points. Bidirectional text is not preserved.
  • Qt: Like Firefox and Chrome, displays one mask for each non-zero 16-bit piece of a code point. Unlike the browsers, the caret position is based on the masks instead of the underlying text (i.e., the caret can be positioned inside of individual code points that do not belong to the basic multilingual plane). Deletion is inconsistent: backspace is based on code points, but delete is based on masks. This results in a complete mess: you can enter an emoji into the password box, position the caret in the middle, delete the second mask, and end up with a single character in the box that is presumably 0x01.

I think that GTK+3 has the most sane approach of these four examples and this approach will be the easiest to implement in Go. Ideally we might do something like displaying one mask for each "character", but determining what code points constitute a character is generally a fraught endeavor and in any case will complicate the code.

My current plan is to modify the code to display one mask rune per underlying code point (no normalization or anything), and then alter editBuffer or its users as necessary so that the caret correctly behaves on a code point basis.

~eliasnaur a month ago

I don't have much to add other than I really appreciate the care you put into your analysis. I agree that the GTK+3 approach sounds like the better option.

~eliasnaur a month ago

Note that it's also quite possible that a layered maskReader is the wrong design; it seemed the most sensible when I thought about the problem.

~tainted-bit a month ago

Note for web readers that this issue is being worked on in patches 11119 and 11185.

~eliasnaur REPORTED FIXED a month ago

Register here or Log in to comment, or comment via email.