Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d707c573d8 | |||
| 69d64f6b62 | |||
| 19f1ef99c7 | |||
| e519d62cff | |||
| da23604acf | |||
| 6b25d52632 | |||
| 5df9c684d8 | |||
| e1aad7bfaf | |||
| 3441b4abcf | |||
| 273dfe7ddd | |||
| 8cb6705a93 | |||
| 04d6b2f3b7 | |||
| 6eebef3585 | |||
| 1b9637825b | |||
| c6ffcfb29e |
@@ -63,7 +63,7 @@ func TestFile(t *testing.T) {
|
||||
{
|
||||
name: "tags.py",
|
||||
code: "<>",
|
||||
want: lines(`<span class="o"><></span>`),
|
||||
want: lines(`<span class="o"><</span><span class="o">></span>`),
|
||||
lexerName: "Python",
|
||||
},
|
||||
{
|
||||
@@ -102,7 +102,7 @@ c=2
|
||||
<span class="n">def</span><span class="p">:</span>\n
|
||||
<span class="n">a</span><span class="o">=</span><span class="mi">1</span>\n
|
||||
\n
|
||||
<span class="n">b</span><span class="o">=</span><span class="s1">''</span>\n
|
||||
<span class="n">b</span><span class="o">=</span><span class="sa"></span><span class="s1">'</span><span class="s1">'</span>\n
|
||||
\n
|
||||
<span class="n">c</span><span class="o">=</span><span class="mi">2</span>`,
|
||||
),
|
||||
@@ -114,18 +114,6 @@ c=2
|
||||
want: []template.HTML{"<span class=\"c1\">--\n</span>", `<span class="k">SELECT</span>`},
|
||||
lexerName: "SQL",
|
||||
},
|
||||
{
|
||||
name: "test.http",
|
||||
code: `HTTP/1.0 400 Bad request
|
||||
Content-Type: text/html
|
||||
|
||||
<html></html>`,
|
||||
want: lines(`<span class="kr">HTTP</span><span class="o">/</span><span class="m">1.0</span> <span class="m">400</span> <span class="ne">Bad request</span>\n
|
||||
<span class="n">Content-Type</span><span class="o">:</span> <span class="l">text/html</span>\n
|
||||
\n
|
||||
<span class="p"><</span><span class="nt">html</span><span class="p">></</span><span class="nt">html</span><span class="p">></span>`),
|
||||
lexerName: "HTTP",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
||||
@@ -288,24 +288,24 @@ func detectChromaLexerWithAnalyze(fileName, lang string, code []byte) chroma.Lex
|
||||
|
||||
// if lang is provided, and it matches a lexer, use it directly
|
||||
if byLang {
|
||||
return chroma.Coalesce(lexer)
|
||||
return lexer
|
||||
}
|
||||
|
||||
// if a lexer is detected and there is no conflict for the file extension, use it directly
|
||||
fileExt := path.Ext(fileName)
|
||||
_, hasConflicts := chromaLexers().conflictingExtLangMap[fileExt]
|
||||
if !hasConflicts && lexer != lexers.Fallback {
|
||||
return chroma.Coalesce(lexer)
|
||||
return lexer
|
||||
}
|
||||
|
||||
// try to detect language by content, for best guessing for the language
|
||||
// when using "code" to detect, analyze.GetCodeLanguage is slow, it iterates many rules to detect language from content
|
||||
analyzedLanguage := analyze.GetCodeLanguage(fileName, code)
|
||||
lexer, _ = detectChromaLexerByFileName(fileName, analyzedLanguage)
|
||||
lexer = DetectChromaLexerByFileName(fileName, analyzedLanguage)
|
||||
if lexer == lexers.Fallback {
|
||||
if analyzedLanguage != enry.OtherLanguage {
|
||||
log.Warn("No chroma lexer found for enry detected language: %s (file: %s), need to fix the language mapping between enry and chroma.", analyzedLanguage, fileName)
|
||||
}
|
||||
}
|
||||
return chroma.Coalesce(lexer)
|
||||
return lexer
|
||||
}
|
||||
|
||||
@@ -39,13 +39,6 @@ var (
|
||||
Channel: "stable",
|
||||
}
|
||||
|
||||
// LoginNotification configuration for sign-in alerts
|
||||
LoginNotification = struct {
|
||||
Enabled bool
|
||||
}{
|
||||
Enabled: true,
|
||||
}
|
||||
|
||||
// IsInTesting indicates whether the testing is running (unit test or integration test). It can be used for:
|
||||
// * Skip nonsense error logs during testing caused by unreliable code (TODO: this is only a temporary solution, we should make the test code more reliable)
|
||||
// * Panic in dev or testing mode to make the problem more obvious and easier to debug
|
||||
@@ -178,7 +171,6 @@ func loadCommonSettingsFrom(cfg ConfigProvider) error {
|
||||
loadOtherFrom(cfg)
|
||||
loadUpdateCheckerFrom(cfg)
|
||||
loadNtfyFrom(cfg)
|
||||
loadLoginNotificationFrom(cfg)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -189,11 +181,6 @@ func loadUpdateCheckerFrom(cfg ConfigProvider) {
|
||||
UpdateChecker.Channel = sec.Key("CHANNEL").MustString(UpdateChecker.Channel)
|
||||
}
|
||||
|
||||
func loadLoginNotificationFrom(cfg ConfigProvider) {
|
||||
sec := cfg.Section("login_notification")
|
||||
LoginNotification.Enabled = sec.Key("ENABLED").MustBool(true)
|
||||
}
|
||||
|
||||
func loadRunModeFrom(rootCfg ConfigProvider) {
|
||||
rootSec := rootCfg.Section("")
|
||||
RunUser = rootSec.Key("RUN_USER").MustString(user.CurrentUsername())
|
||||
|
||||
@@ -440,9 +440,6 @@ func handleSignInFull(ctx *context.Context, u *user_model.User, remember bool) {
|
||||
ctx.ServerError("UpdateUser", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Send login notification (email + ntfy)
|
||||
go mailer.SendLoginNotification(u, ctx.RemoteAddr(), ctx.Req.UserAgent())
|
||||
}
|
||||
|
||||
// extractUserNameFromOAuth2 tries to extract a normalized username from the given OAuth2 user.
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
// Copyright 2026 Moko Consulting <hello@mokoconsulting.tech>
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package mailer
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
user_model "git.mokoconsulting.tech/MokoConsulting/MokoGitea/models/user"
|
||||
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/log"
|
||||
"git.mokoconsulting.tech/MokoConsulting/MokoGitea/modules/setting"
|
||||
sender_service "git.mokoconsulting.tech/MokoConsulting/MokoGitea/services/mailer/sender"
|
||||
)
|
||||
|
||||
// SendLoginNotification sends email and ntfy notifications when a user signs in.
|
||||
func SendLoginNotification(u *user_model.User, ip, userAgent string) {
|
||||
if !setting.LoginNotification.Enabled {
|
||||
return
|
||||
}
|
||||
|
||||
timestamp := time.Now().UTC().Format("2006-01-02 15:04:05 UTC")
|
||||
subject := fmt.Sprintf("[%s] New sign-in: %s", setting.AppName, u.Name)
|
||||
|
||||
body := fmt.Sprintf(`New sign-in detected
|
||||
|
||||
Account: %s (%s)
|
||||
IP Address: %s
|
||||
Browser: %s
|
||||
Time: %s
|
||||
Instance: %s
|
||||
|
||||
If this wasn't you, change your password immediately and review your active sessions.
|
||||
|
||||
— %s`, u.Name, u.Email, ip, userAgent, timestamp, setting.AppURL, setting.AppName)
|
||||
|
||||
// Email notification
|
||||
if setting.MailService != nil && u.Email != "" {
|
||||
msg := sender_service.NewMessage(u.EmailTo(), subject, body)
|
||||
msg.Info = fmt.Sprintf("Login notification for %s", u.Name)
|
||||
SendAsync(msg)
|
||||
log.Debug("Login notification email sent to %s", u.Email)
|
||||
}
|
||||
|
||||
// ntfy push notification
|
||||
if setting.Ntfy.Enabled && setting.Ntfy.ServerURL != "" {
|
||||
go sendLoginNtfy(subject, u.Name, ip, timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
func sendLoginNtfy(title, username, ip, timestamp string) {
|
||||
body := fmt.Sprintf("User: %s\nIP: %s\nTime: %s", username, ip, timestamp)
|
||||
url := fmt.Sprintf("%s/%s", setting.Ntfy.ServerURL, setting.Ntfy.DefaultTopic)
|
||||
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBufferString(body))
|
||||
if err != nil {
|
||||
log.Error("ntfy login: create request: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
req.Header.Set("Title", title)
|
||||
req.Header.Set("Priority", "default")
|
||||
req.Header.Set("Tags", "key,login")
|
||||
req.Header.Set("Click", setting.AppURL+"-/admin")
|
||||
if setting.Ntfy.Token != "" {
|
||||
req.Header.Set("Authorization", "Bearer "+setting.Ntfy.Token)
|
||||
}
|
||||
|
||||
client := &http.Client{Timeout: 10 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Error("ntfy login: send: %v", err)
|
||||
return
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
io.Copy(io.Discard, resp.Body)
|
||||
|
||||
if resp.StatusCode >= 300 {
|
||||
log.Error("ntfy login: status %d", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,6 @@
|
||||
</div>
|
||||
<a href="{{AssetUrlPrefix}}/licenses.txt">{{ctx.Locale.Tr "licenses"}}</a>
|
||||
{{if .EnableSwagger}}<a href="{{AppSubUrl}}/api/swagger">API</a>{{end}}
|
||||
<a href="{{HelpURL}}" target="_blank">{{ctx.Locale.Tr "help"}}</a>
|
||||
{{template "custom/extra_links_footer" .}}
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -35,7 +35,9 @@
|
||||
|
||||
{{template "custom/extra_links" .}}
|
||||
|
||||
<a class="item" target="_blank" href="{{HelpURL}}">{{ctx.Locale.Tr "help"}}</a>
|
||||
{{if not .IsSigned}}
|
||||
<a class="item" target="_blank" href="{{HelpURL}}">{{ctx.Locale.Tr "help"}}</a>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
<!-- the full dropdown menus -->
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
<div class="ui container fluid">
|
||||
<div class="tw-text-center tw-mb-4">
|
||||
<img src="{{AssetUrlPrefix}}/img/login-logo.png" style="max-width: 220px; max-height: 80px; object-fit: contain;" onerror="this.style.display='none'">
|
||||
</div>
|
||||
{{if or (not .LinkAccountMode) (and .LinkAccountMode .LinkAccountModeSignIn)}}
|
||||
{{template "base/alert" .}}
|
||||
{{end}}
|
||||
|
||||
Reference in New Issue
Block a user