From 9ef9e16625174c0c76f795e58dea065d626983d9 Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Mon, 31 Jan 2022 21:15:38 +0500 Subject: [PATCH] :sparkles: Added error page template --- catalog_gen.go | 37 ++- internal/auth/delivery/http/auth_http.go | 86 +++--- internal/ticket/delivery/http/ticket_http.go | 15 +- locales/en/messages.gotext.json | 42 +++ locales/en/out.gotext.json | 14 + locales/ru/messages.gotext.json | 30 ++ locales/ru/out.gotext.json | 10 + web/authorize.qtpl | 26 +- web/authorize.qtpl.go | 290 +++++++++++-------- web/error.qtpl | 42 +++ web/error.qtpl.go | 158 ++++++++++ 11 files changed, 563 insertions(+), 187 deletions(-) create mode 100644 web/error.qtpl create mode 100644 web/error.qtpl.go diff --git a/catalog_gen.go b/catalog_gen.go index a719630..c275c4b 100644 --- a/catalog_gen.go +++ b/catalog_gen.go @@ -44,31 +44,36 @@ var messageKeyToIndex = map[string]int{ "Authorize application": 1, "Choose your scopes": 2, "Deny": 3, - "Recipient": 7, - "Resource": 8, - "Send": 9, - "Sign In": 5, - "TicketAuth": 6, + "Error": 5, + "How do I fix it?": 6, + "Recipient": 9, + "Resource": 10, + "Send": 11, + "Sign In": 7, + "TicketAuth": 8, } -var enIndex = []uint32{ // 11 elements +var enIndex = []uint32{ // 13 elements 0x00000000, 0x00000010, 0x00000026, 0x00000039, - 0x0000003e, 0x00000044, 0x0000004c, 0x00000057, - 0x00000061, 0x0000006a, 0x0000006f, -} // Size: 68 bytes + 0x0000003e, 0x00000044, 0x0000004a, 0x0000005b, + 0x00000063, 0x0000006e, 0x00000078, 0x00000081, + 0x00000086, +} // Size: 76 bytes -const enData string = "" + // Size: 111 bytes +const enData string = "" + // Size: 134 bytes "\x02Authorize %[1]s\x02Authorize application\x02Choose your scopes\x02De" + - "ny\x02Allow\x02Sign In\x02TicketAuth\x02Recipient\x02Resource\x02Send" + "ny\x02Allow\x02Error\x02How do I fix it?\x02Sign In\x02TicketAuth\x02Rec" + + "ipient\x02Resource\x02Send" -var ruIndex = []uint32{ // 11 elements +var ruIndex = []uint32{ // 13 elements 0x00000000, 0x0000001f, 0x0000004d, 0x0000008e, - 0x0000009f, 0x000000b2, 0x000000bd, 0x000000bd, - 0x000000bd, 0x000000bd, 0x000000bd, -} // Size: 68 bytes + 0x0000009f, 0x000000b2, 0x000000b2, 0x000000b2, + 0x000000bd, 0x000000bd, 0x000000bd, 0x000000bd, + 0x000000bd, +} // Size: 76 bytes const ruData string = "" + // Size: 189 bytes "\x02Авторизовать %[1]s\x02Авторизовать приложение\x02Выбери предоставляе" + "мые разрешения\x02Отказать\x02Разрешить\x02Войти" - // Total table size 436 bytes (0KiB); checksum: BBD74FCF + // Total table size 475 bytes (0KiB); checksum: F5D99818 diff --git a/internal/auth/delivery/http/auth_http.go b/internal/auth/delivery/http/auth_http.go index 3275aa4..6e175c1 100644 --- a/internal/auth/delivery/http/auth_http.go +++ b/internal/auth/delivery/http/auth_http.go @@ -1,6 +1,7 @@ package http import ( + "bytes" "crypto/subtle" "errors" "path" @@ -159,36 +160,54 @@ func (h *RequestHandler) Register(r *router.Router) { func (h *RequestHandler) handleRender(ctx *http.RequestCtx) { req := new(AuthorizeRequest) + ctx.SetContentType(common.MIMETextHTMLCharsetUTF8) + + tags, _, _ := language.ParseAcceptLanguage(string(ctx.Request.Header.Peek(http.HeaderAcceptLanguage))) + tag, _, _ := h.matcher.Match(tags...) + baseOf := web.BaseOf{ + Config: h.config, + Language: tag, + Printer: message.NewPrinter(tag), + } + if err := req.bind(ctx); err != nil { - ctx.Error(err.Error(), http.StatusBadRequest) + ctx.SetStatusCode(http.StatusBadRequest) + web.WriteTemplate(ctx, &web.ErrorPage{ + BaseOf: baseOf, + Error: err, + }) return } client, err := h.clients.Discovery(ctx, req.ClientID) if err != nil { - ctx.Error(err.Error(), http.StatusBadRequest) + ctx.SetStatusCode(http.StatusBadRequest) + web.WriteTemplate(ctx, &web.ErrorPage{ + BaseOf: baseOf, + Error: err, + }) return } if !client.ValidateRedirectURI(req.RedirectURI) { - ctx.Error("requested redirect_uri is not registered on client_id side", http.StatusBadRequest) + ctx.SetStatusCode(http.StatusBadRequest) + web.WriteTemplate(ctx, &web.ErrorPage{ + BaseOf: baseOf, + Error: domain.NewError( + domain.ErrorCodeInvalidClient, + "requested redirect_uri is not registered on client_id side", + "", + ), + }) return } csrf, _ := ctx.UserValue(middleware.DefaultCSRFConfig.ContextKey).([]byte) - tags, _, _ := language.ParseAcceptLanguage(string(ctx.Request.Header.Peek(http.HeaderAcceptLanguage))) - tag, _, _ := h.matcher.Match(tags...) - - ctx.SetContentType(common.MIMETextHTMLCharsetUTF8) web.WriteTemplate(ctx, &web.AuthorizePage{ - BaseOf: web.BaseOf{ - Config: h.config, - Language: tag, - Printer: message.NewPrinter(tag), - }, + BaseOf: baseOf, Client: client, CodeChallenge: req.CodeChallenge, CodeChallengeMethod: req.CodeChallengeMethod, @@ -298,8 +317,7 @@ func (r *AuthorizeRequest) bind(ctx *http.RequestCtx) error { } r.Scope = make(domain.Scopes, 0) - - if err := parseScope(r.Scope, ctx.QueryArgs().Peek("scope")); err != nil { + if err := r.Scope.UnmarshalForm(ctx.QueryArgs().Peek("scope")); err != nil { if errors.As(err, indieAuthError) { return indieAuthError } @@ -334,7 +352,17 @@ func (r *VerifyRequest) bind(ctx *http.RequestCtx) error { } r.Scope = make(domain.Scopes, 0) - parseScope(r.Scope, ctx.PostArgs().PeekMulti("scope[]")...) + if err := r.Scope.UnmarshalForm(bytes.Join(ctx.PostArgs().PeekMulti("scope[]"), []byte(" "))); err != nil { + if errors.As(err, indieAuthError) { + return indieAuthError + } + + return domain.NewError( + domain.ErrorCodeInvalidScope, + err.Error(), + "https://indieweb.org/scope", + ) + } if r.ResponseType == domain.ResponseTypeID { r.ResponseType = domain.ResponseTypeCode @@ -369,31 +397,3 @@ func (r *ExchangeRequest) bind(ctx *http.RequestCtx) error { return nil } - -// TODO(toby3d): fix this in form pkg. -func parseScope(dst domain.Scopes, src ...[]byte) error { - if len(src) == 0 { - return nil - } - - var scopes []string - - if len(src) == 1 { - scopes = strings.Fields(string(src[0])) - } - - for _, rawScope := range scopes { - scope, err := domain.ParseScope(string(rawScope)) - if err != nil { - return domain.NewError( - domain.ErrorCodeInvalidScope, - err.Error(), - "https://indieweb.org/scope#IndieAuth_Scopes", - ) - } - - dst = append(dst, scope) - } - - return nil -} diff --git a/internal/ticket/delivery/http/ticket_http.go b/internal/ticket/delivery/http/ticket_http.go index 754e6bb..1a08e85 100644 --- a/internal/ticket/delivery/http/ticket_http.go +++ b/internal/ticket/delivery/http/ticket_http.go @@ -83,17 +83,16 @@ func (h *RequestHandler) handleRender(ctx *http.RequestCtx) { tags, _, _ := language.ParseAcceptLanguage(string(ctx.Request.Header.Peek(http.HeaderAcceptLanguage))) tag, _, _ := h.matcher.Match(tags...) + baseOf := web.BaseOf{ + Config: h.config, + Language: tag, + Printer: message.NewPrinter(tag), + } csrf, _ := ctx.UserValue("csrf").([]byte) - - ctx.SetContentType(common.MIMETextHTMLCharsetUTF8) web.WriteTemplate(ctx, &web.TicketPage{ - BaseOf: web.BaseOf{ - Config: h.config, - Language: tag, - Printer: message.NewPrinter(tag), - }, - CSRF: csrf, + BaseOf: baseOf, + CSRF: csrf, }) } diff --git a/locales/en/messages.gotext.json b/locales/en/messages.gotext.json index f1475b6..19616d5 100644 --- a/locales/en/messages.gotext.json +++ b/locales/en/messages.gotext.json @@ -46,12 +46,54 @@ "translatorComment": "The name of the button to continue the application authorization process", "fuzzy": true }, + { + "id": "Error", + "message": "Error", + "translation": "Error", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "How do I fix it?", + "message": "How do I fix it?", + "translation": "How do I fix it?", + "translatorComment": "Copied from source.", + "fuzzy": true + }, { "id": "Sign In", "message": "Sign In", "translation": "Sign In", "translatorComment": "The name of the button in the site address entry form to start the login process", "fuzzy": true + }, + { + "id": "TicketAuth", + "message": "TicketAuth", + "translation": "TicketAuth", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Recipient", + "message": "Recipient", + "translation": "Recipient", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Resource", + "message": "Resource", + "translation": "Resource", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "Send", + "message": "Send", + "translation": "Send", + "translatorComment": "Copied from source.", + "fuzzy": true } ] } \ No newline at end of file diff --git a/locales/en/out.gotext.json b/locales/en/out.gotext.json index 6920416..19616d5 100644 --- a/locales/en/out.gotext.json +++ b/locales/en/out.gotext.json @@ -46,6 +46,20 @@ "translatorComment": "The name of the button to continue the application authorization process", "fuzzy": true }, + { + "id": "Error", + "message": "Error", + "translation": "Error", + "translatorComment": "Copied from source.", + "fuzzy": true + }, + { + "id": "How do I fix it?", + "message": "How do I fix it?", + "translation": "How do I fix it?", + "translatorComment": "Copied from source.", + "fuzzy": true + }, { "id": "Sign In", "message": "Sign In", diff --git a/locales/ru/messages.gotext.json b/locales/ru/messages.gotext.json index 38d4349..2259d0f 100644 --- a/locales/ru/messages.gotext.json +++ b/locales/ru/messages.gotext.json @@ -39,11 +39,41 @@ "translation": "Разрешить", "translatorComment": "Название кнопки продолжения процесса авторизациии приложения" }, + { + "id": "Error", + "message": "Error", + "translation": "Ошибка" + }, + { + "id": "How do I fix it?", + "message": "How do I fix it?", + "translation": "Как исправить это?" + }, { "id": "Sign In", "message": "Sign In", "translation": "Войти", "translatorComment": "Название кнопки в форме ввода адреса сайта для начала процесса входа" + }, + { + "id": "TicketAuth", + "message": "TicketAuth", + "translation": "TicketAuth" + }, + { + "id": "Recipient", + "message": "Recipient", + "translation": "Получатель" + }, + { + "id": "Resource", + "message": "Resource", + "translation": "Ресурс" + }, + { + "id": "Send", + "message": "Send", + "translation": "Отправить" } ] } \ No newline at end of file diff --git a/locales/ru/out.gotext.json b/locales/ru/out.gotext.json index 9de77a9..4873762 100644 --- a/locales/ru/out.gotext.json +++ b/locales/ru/out.gotext.json @@ -39,6 +39,16 @@ "translation": "Разрешить", "translatorComment": "Название кнопки продолжения процесса авторизациии приложения" }, + { + "id": "Error", + "message": "Error", + "translation": "" + }, + { + "id": "How do I fix it?", + "message": "How do I fix it?", + "translation": "" + }, { "id": "Sign In", "message": "Sign In", diff --git a/web/authorize.qtpl b/web/authorize.qtpl index 3a8a3ea..2a3a5b8 100644 --- a/web/authorize.qtpl +++ b/web/authorize.qtpl @@ -2,13 +2,14 @@ {% code type AuthorizePage struct { BaseOf + CSRF []byte + Providers []*domain.Provider + Scope domain.Scopes Client *domain.Client - RedirectURI *domain.URL Me *domain.Me + RedirectURI *domain.URL CodeChallengeMethod domain.CodeChallengeMethod ResponseType domain.ResponseType - Scope domain.Scopes - CSRF []byte CodeChallenge string State string } %} @@ -119,6 +120,25 @@ value="{%s p.Me.String() %}"> {% endif %} + {% if len(p.Providers) > 0 %} + + {% else %} + + {% endif %} + @@ -352,39 +408,39 @@ func (p *AuthorizePage) StreamBody(qw422016 *qt422016.Writer) { value="allow"> `) -//line web/authorize.qtpl:135 +//line web/authorize.qtpl:155 p.StreamT(qw422016, "Allow") -//line web/authorize.qtpl:135 +//line web/authorize.qtpl:155 qw422016.N().S(` `) -//line web/authorize.qtpl:139 +//line web/authorize.qtpl:159 } -//line web/authorize.qtpl:139 +//line web/authorize.qtpl:159 func (p *AuthorizePage) WriteBody(qq422016 qtio422016.Writer) { -//line web/authorize.qtpl:139 +//line web/authorize.qtpl:159 qw422016 := qt422016.AcquireWriter(qq422016) -//line web/authorize.qtpl:139 +//line web/authorize.qtpl:159 p.StreamBody(qw422016) -//line web/authorize.qtpl:139 +//line web/authorize.qtpl:159 qt422016.ReleaseWriter(qw422016) -//line web/authorize.qtpl:139 +//line web/authorize.qtpl:159 } -//line web/authorize.qtpl:139 +//line web/authorize.qtpl:159 func (p *AuthorizePage) Body() string { -//line web/authorize.qtpl:139 +//line web/authorize.qtpl:159 qb422016 := qt422016.AcquireByteBuffer() -//line web/authorize.qtpl:139 +//line web/authorize.qtpl:159 p.WriteBody(qb422016) -//line web/authorize.qtpl:139 +//line web/authorize.qtpl:159 qs422016 := string(qb422016.B) -//line web/authorize.qtpl:139 +//line web/authorize.qtpl:159 qt422016.ReleaseByteBuffer(qb422016) -//line web/authorize.qtpl:139 +//line web/authorize.qtpl:159 return qs422016 -//line web/authorize.qtpl:139 +//line web/authorize.qtpl:159 } diff --git a/web/error.qtpl b/web/error.qtpl new file mode 100644 index 0000000..43a5cdd --- /dev/null +++ b/web/error.qtpl @@ -0,0 +1,42 @@ +{% import ( + "errors" + + "source.toby3d.me/website/indieauth/internal/domain" +) %} + +{% code type ErrorPage struct { + BaseOf + Error error +} %} + +{% collapsespace %} +{% func (p *ErrorPage) Title() %} + {%s p.T("Error") %} +{% endfunc %} + +{% func (p *ErrorPage) Body() %} +
+ {% code err := new(domain.Error) %} + {% if errors.As(p.Error, err) %} +

{%s err.Code.String() %}

+ + {% if err.Description != "" %} +

{%s err.Description %}

+ {% endif %} + + {% if err.URI != "" %} + + + {%s p.T("How do I fix it?") %} + + {% endif %} + {% else %} +

{%s p.T("Error") %}

+

{%s p.Error.Error() %}

+ {% endif %} +
+{% endfunc %} +{% endcollapsespace %} diff --git a/web/error.qtpl.go b/web/error.qtpl.go new file mode 100644 index 0000000..74013d9 --- /dev/null +++ b/web/error.qtpl.go @@ -0,0 +1,158 @@ +// Code generated by qtc from "error.qtpl". DO NOT EDIT. +// See https://github.com/valyala/quicktemplate for details. + +//line web/error.qtpl:1 +package web + +//line web/error.qtpl:1 +import ( + "errors" + + "source.toby3d.me/website/indieauth/internal/domain" +) + +//line web/error.qtpl:7 +import ( + qtio422016 "io" + + qt422016 "github.com/valyala/quicktemplate" +) + +//line web/error.qtpl:7 +var ( + _ = qtio422016.Copy + _ = qt422016.AcquireByteBuffer +) + +//line web/error.qtpl:7 +type ErrorPage struct { + BaseOf + Error error +} + +//line web/error.qtpl:13 +func (p *ErrorPage) StreamTitle(qw422016 *qt422016.Writer) { +//line web/error.qtpl:13 + qw422016.N().S(` `) +//line web/error.qtpl:14 + qw422016.E().S(p.T("Error")) +//line web/error.qtpl:14 + qw422016.N().S(` `) +//line web/error.qtpl:15 +} + +//line web/error.qtpl:15 +func (p *ErrorPage) WriteTitle(qq422016 qtio422016.Writer) { +//line web/error.qtpl:15 + qw422016 := qt422016.AcquireWriter(qq422016) +//line web/error.qtpl:15 + p.StreamTitle(qw422016) +//line web/error.qtpl:15 + qt422016.ReleaseWriter(qw422016) +//line web/error.qtpl:15 +} + +//line web/error.qtpl:15 +func (p *ErrorPage) Title() string { +//line web/error.qtpl:15 + qb422016 := qt422016.AcquireByteBuffer() +//line web/error.qtpl:15 + p.WriteTitle(qb422016) +//line web/error.qtpl:15 + qs422016 := string(qb422016.B) +//line web/error.qtpl:15 + qt422016.ReleaseByteBuffer(qb422016) +//line web/error.qtpl:15 + return qs422016 +//line web/error.qtpl:15 +} + +//line web/error.qtpl:17 +func (p *ErrorPage) StreamBody(qw422016 *qt422016.Writer) { +//line web/error.qtpl:17 + qw422016.N().S(`
`) +//line web/error.qtpl:19 + err := new(domain.Error) + +//line web/error.qtpl:19 + qw422016.N().S(` `) +//line web/error.qtpl:20 + if errors.As(p.Error, err) { +//line web/error.qtpl:20 + qw422016.N().S(`

`) +//line web/error.qtpl:21 + qw422016.E().S(err.Code.String()) +//line web/error.qtpl:21 + qw422016.N().S(`

`) +//line web/error.qtpl:23 + if err.Description != "" { +//line web/error.qtpl:23 + qw422016.N().S(`

`) +//line web/error.qtpl:24 + qw422016.E().S(err.Description) +//line web/error.qtpl:24 + qw422016.N().S(`

`) +//line web/error.qtpl:25 + } +//line web/error.qtpl:25 + qw422016.N().S(` `) +//line web/error.qtpl:27 + if err.URI != "" { +//line web/error.qtpl:27 + qw422016.N().S(` `) +//line web/error.qtpl:33 + qw422016.E().S(p.T("How do I fix it?")) +//line web/error.qtpl:33 + qw422016.N().S(` `) +//line web/error.qtpl:35 + } +//line web/error.qtpl:35 + qw422016.N().S(` `) +//line web/error.qtpl:36 + } else { +//line web/error.qtpl:36 + qw422016.N().S(`

`) +//line web/error.qtpl:37 + qw422016.E().S(p.T("Error")) +//line web/error.qtpl:37 + qw422016.N().S(`

`) +//line web/error.qtpl:38 + qw422016.E().S(p.Error.Error()) +//line web/error.qtpl:38 + qw422016.N().S(`

`) +//line web/error.qtpl:39 + } +//line web/error.qtpl:39 + qw422016.N().S(`
`) +//line web/error.qtpl:41 +} + +//line web/error.qtpl:41 +func (p *ErrorPage) WriteBody(qq422016 qtio422016.Writer) { +//line web/error.qtpl:41 + qw422016 := qt422016.AcquireWriter(qq422016) +//line web/error.qtpl:41 + p.StreamBody(qw422016) +//line web/error.qtpl:41 + qt422016.ReleaseWriter(qw422016) +//line web/error.qtpl:41 +} + +//line web/error.qtpl:41 +func (p *ErrorPage) Body() string { +//line web/error.qtpl:41 + qb422016 := qt422016.AcquireByteBuffer() +//line web/error.qtpl:41 + p.WriteBody(qb422016) +//line web/error.qtpl:41 + qs422016 := string(qb422016.B) +//line web/error.qtpl:41 + qt422016.ReleaseByteBuffer(qb422016) +//line web/error.qtpl:41 + return qs422016 +//line web/error.qtpl:41 +}