add user telegram notifications
This commit is contained in:
committed by
Umputun
parent
7f081e1d2c
commit
200733ed03
@@ -18,7 +18,7 @@ Remark42 is a self-hosted, lightweight, and simple (yet functional) comment engi
|
||||
* Extractor for recent comments, cross-post
|
||||
* RSS for all comments and each post
|
||||
* Telegram, Slack and email notifications for Admins (get notified for each new comment)
|
||||
* Email notifications for users (get notified when someone responds to your comment)
|
||||
* Email and Telegram notifications for users (get notified when someone responds to your comment)
|
||||
* Export data to json with automatic backups
|
||||
* No external databases, everything embedded in a single data file
|
||||
* Fully dockerized and can be deployed in a single command
|
||||
@@ -156,7 +156,7 @@ _this is the recommended way to run remark42_
|
||||
| auth.email.subj | AUTH_EMAIL_SUBJ | `remark42 confirmation` | email subject |
|
||||
| auth.email.content-type | AUTH_EMAIL_CONTENT_TYPE | `text/html` | email content type |
|
||||
| auth.email.template | AUTH_EMAIL_TEMPLATE | none (predefined) | custom email message template file |
|
||||
| notify.users | NOTIFY_USERS | none | type of user notifications (email) |
|
||||
| notify.users | NOTIFY_USERS | none | type of user notifications (telegram, email) |
|
||||
| notify.admins | NOTIFY_ADMINS | none | type of admin notifications (telegram, slack and/or email) |
|
||||
| notify.queue | NOTIFY_QUEUE | `100` | size of notification queue |
|
||||
| notify.telegram.chan | NOTIFY_TELEGRAM_CHAN | | telegram channel |
|
||||
|
||||
+60
-42
@@ -211,7 +211,7 @@ type SMTPGroup struct {
|
||||
// NotifyGroup defines options for notification
|
||||
type NotifyGroup struct {
|
||||
Type []string `long:"type" env:"TYPE" description:"[deprecated, use user and admin types instead] types of notifications" choice:"none" choice:"telegram" choice:"email" choice:"slack" default:"none" env-delim:","` //nolint
|
||||
Users []string `long:"users" env:"USERS" description:"types of user notifications" choice:"none" choice:"email" default:"none" env-delim:","` //nolint
|
||||
Users []string `long:"users" env:"USERS" description:"types of user notifications" choice:"none" choice:"email" choice:"telegram" default:"none" env-delim:","` //nolint
|
||||
Admins []string `long:"admins" env:"ADMINS" description:"types of admin notifications" choice:"none" choice:"telegram" choice:"email" choice:"slack" default:"none" env-delim:","` //nolint
|
||||
QueueSize int `long:"queue" env:"QUEUE" description:"size of notification queue" default:"100"`
|
||||
Telegram struct {
|
||||
@@ -462,16 +462,22 @@ func (s *ServerCommand) newServerApp() (*serverApp, error) {
|
||||
}
|
||||
|
||||
var emailNotifications bool
|
||||
notifyService, err := s.makeNotify(dataService, authenticator)
|
||||
var telegramNotificationsBotUsername string
|
||||
notifyService, telegramBotUsername, err := s.makeNotify(dataService, authenticator)
|
||||
|
||||
if contains("email", s.Notify.Users) {
|
||||
emailNotifications = true
|
||||
}
|
||||
|
||||
if contains("telegram", s.Notify.Users) {
|
||||
telegramNotificationsBotUsername = telegramBotUsername
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Printf("[WARN] failed to make notify service, %s", err)
|
||||
notifyService = notify.NopService // disable notifier
|
||||
emailNotifications = false // email notifications are not available in this case
|
||||
notifyService = notify.NopService // disable notifier
|
||||
emailNotifications = false // email notifications are not available in this case
|
||||
telegramNotificationsBotUsername = "" // telegram notifications are not available in this case either
|
||||
}
|
||||
|
||||
imgProxy := &proxy.Image{
|
||||
@@ -494,28 +500,29 @@ func (s *ServerCommand) newServerApp() (*serverApp, error) {
|
||||
}
|
||||
|
||||
srv := &api.Rest{
|
||||
Version: s.Revision,
|
||||
DataService: dataService,
|
||||
WebRoot: s.WebRoot,
|
||||
RemarkURL: s.RemarkURL,
|
||||
ImageProxy: imgProxy,
|
||||
CommentFormatter: commentFormatter,
|
||||
Migrator: migr,
|
||||
ReadOnlyAge: s.ReadOnlyAge,
|
||||
SharedSecret: s.SharedSecret,
|
||||
Authenticator: authenticator,
|
||||
Cache: loadingCache,
|
||||
NotifyService: notifyService,
|
||||
SSLConfig: sslConfig,
|
||||
UpdateLimiter: s.UpdateLimit,
|
||||
ImageService: imageService,
|
||||
EmailNotifications: emailNotifications,
|
||||
EmojiEnabled: s.EnableEmoji,
|
||||
AnonVote: s.AnonymousVote && s.RestrictVoteIP,
|
||||
SimpleView: s.SimpleView,
|
||||
ProxyCORS: s.ProxyCORS,
|
||||
AllowedAncestors: s.AllowedHosts,
|
||||
SendJWTHeader: s.Auth.SendJWTHeader,
|
||||
Version: s.Revision,
|
||||
DataService: dataService,
|
||||
WebRoot: s.WebRoot,
|
||||
RemarkURL: s.RemarkURL,
|
||||
ImageProxy: imgProxy,
|
||||
CommentFormatter: commentFormatter,
|
||||
Migrator: migr,
|
||||
ReadOnlyAge: s.ReadOnlyAge,
|
||||
SharedSecret: s.SharedSecret,
|
||||
Authenticator: authenticator,
|
||||
Cache: loadingCache,
|
||||
NotifyService: notifyService,
|
||||
SSLConfig: sslConfig,
|
||||
UpdateLimiter: s.UpdateLimit,
|
||||
ImageService: imageService,
|
||||
EmailNotifications: emailNotifications,
|
||||
TelegramBotUsername: telegramNotificationsBotUsername,
|
||||
EmojiEnabled: s.EnableEmoji,
|
||||
AnonVote: s.AnonymousVote && s.RestrictVoteIP,
|
||||
SimpleView: s.SimpleView,
|
||||
ProxyCORS: s.ProxyCORS,
|
||||
AllowedAncestors: s.AllowedHosts,
|
||||
SendJWTHeader: s.Auth.SendJWTHeader,
|
||||
}
|
||||
|
||||
srv.ScoreThresholds.Low, srv.ScoreThresholds.Critical = s.LowScore, s.CriticalScore
|
||||
@@ -868,46 +875,57 @@ func (s *ServerCommand) loadEmailTemplate() (string, error) {
|
||||
return string(file), nil
|
||||
}
|
||||
|
||||
func (s *ServerCommand) makeNotify(dataStore *service.DataStore, authenticator *auth.Service) (*notify.Service, error) {
|
||||
// aside from notify.Service and error, returns telegram bot name which will be passed to the frontend
|
||||
func (s *ServerCommand) makeNotify(dataStore *service.DataStore, authenticator *auth.Service) (*notify.Service, string, error) {
|
||||
var notifyService *notify.Service
|
||||
var destinations []notify.Destination
|
||||
var telegramBotUsername string
|
||||
for _, t := range s.Notify.Admins {
|
||||
switch t {
|
||||
case "slack":
|
||||
slack, err := notify.NewSlack(s.Notify.Slack.Token, s.Notify.Slack.Channel)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create slack notification destination")
|
||||
return nil, "", errors.Wrap(err, "failed to create slack notification destination")
|
||||
}
|
||||
destinations = append(destinations, slack)
|
||||
case "telegram":
|
||||
telegramParams := notify.TelegramParams{
|
||||
AdminChannelID: s.Notify.Telegram.Channel,
|
||||
Token: s.Telegram.Token,
|
||||
Timeout: s.Telegram.Timeout,
|
||||
}
|
||||
tg, err := notify.NewTelegram(telegramParams)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create telegram notification destination")
|
||||
}
|
||||
destinations = append(destinations, tg)
|
||||
case "email":
|
||||
case "none":
|
||||
notifyService = notify.NopService
|
||||
default:
|
||||
return nil, errors.Errorf("unsupported admin notification type %q", s.Notify.Type)
|
||||
return nil, "", errors.Errorf("unsupported admin notification type %q", s.Notify.Type)
|
||||
}
|
||||
}
|
||||
|
||||
for _, t := range s.Notify.Users {
|
||||
switch t {
|
||||
case "telegram":
|
||||
case "email":
|
||||
case "none":
|
||||
notifyService = notify.NopService
|
||||
default:
|
||||
return nil, errors.Errorf("unsupported user notification type %q", s.Notify.Type)
|
||||
return nil, "", errors.Errorf("unsupported user notification type %q", s.Notify.Type)
|
||||
}
|
||||
}
|
||||
|
||||
if contains("telegram", s.Notify.Users) || contains("telegram", s.Notify.Admins) {
|
||||
if contains("telegram", s.Notify.Admins) && s.Notify.Telegram.Channel == "" {
|
||||
return nil, "", errors.New("--notify.telegram.channel must be set for admin notifications to work")
|
||||
}
|
||||
telegramParams := notify.TelegramParams{
|
||||
AdminChannelID: s.Notify.Telegram.Channel,
|
||||
UserNotifications: contains("telegram", s.Notify.Users),
|
||||
Token: s.Telegram.Token,
|
||||
Timeout: s.Telegram.Timeout,
|
||||
}
|
||||
tg, err := notify.NewTelegram(telegramParams)
|
||||
if err != nil {
|
||||
return nil, "", errors.Wrap(err, "failed to create telegram notification destination")
|
||||
}
|
||||
destinations = append(destinations, tg)
|
||||
telegramBotUsername = tg.BotUsername
|
||||
}
|
||||
|
||||
// with logic below admin notifications enable notifications for users on the backend even if they
|
||||
// are not enabled explicitly, however they won't be visible to the users in the frontend
|
||||
// because api.Rest.EmailNotifications would be set to false.
|
||||
@@ -949,7 +967,7 @@ func (s *ServerCommand) makeNotify(dataStore *service.DataStore, authenticator *
|
||||
}
|
||||
emailService, err := notify.NewEmail(emailParams, smtpParams)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create email notification destination")
|
||||
return nil, "", errors.Wrap(err, "failed to create email notification destination")
|
||||
}
|
||||
destinations = append(destinations, emailService)
|
||||
}
|
||||
@@ -958,7 +976,7 @@ func (s *ServerCommand) makeNotify(dataStore *service.DataStore, authenticator *
|
||||
log.Printf("[INFO] make notify, for users: %s, for admins: %s", s.Notify.Users, s.Notify.Admins)
|
||||
notifyService = notify.NewService(dataStore, s.Notify.QueueSize, destinations...)
|
||||
}
|
||||
return notifyService, nil
|
||||
return notifyService, telegramBotUsername, nil
|
||||
}
|
||||
|
||||
func (s *ServerCommand) makeSSLConfig() (config api.SSLConfig, err error) {
|
||||
|
||||
@@ -51,10 +51,11 @@ type Request struct {
|
||||
|
||||
// VerificationRequest notification for user
|
||||
type VerificationRequest struct {
|
||||
SiteID string
|
||||
User string
|
||||
Email string // if set, send email only
|
||||
Token string
|
||||
SiteID string
|
||||
User string
|
||||
Email string // if set, send email only
|
||||
Telegram string // if set, send telegram only
|
||||
Token string
|
||||
}
|
||||
|
||||
const defaultQueueSize = 100
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
|
||||
log "github.com/go-pkgz/lgr"
|
||||
"github.com/go-pkgz/repeater"
|
||||
"github.com/pkg/errors"
|
||||
@@ -18,9 +20,11 @@ import (
|
||||
|
||||
// TelegramParams contain settings for telegram notifications
|
||||
type TelegramParams struct {
|
||||
AdminChannelID string // unique identifier for the target chat or username of the target channel (in the format @channelusername)
|
||||
Token string // token for telegram bot API interactions
|
||||
Timeout time.Duration // http client timeout
|
||||
AdminChannelID string // unique identifier for the target chat or username of the target channel (in the format @channelusername)
|
||||
Token string // token for telegram bot API interactions
|
||||
Timeout time.Duration // http client timeout
|
||||
BotUsername string // filled with bot username after Telegram creation, used in frontend
|
||||
UserNotifications bool // flag which enables user notifications
|
||||
|
||||
apiPrefix string // changed only in tests
|
||||
}
|
||||
@@ -93,6 +97,8 @@ func NewTelegram(params TelegramParams) (*Telegram, error) {
|
||||
if !tgResp.OK || !tgResp.Result.IsBot {
|
||||
return errors.Errorf("unexpected telegram response %+v", tgResp)
|
||||
}
|
||||
|
||||
res.BotUsername = tgResp.Result.UserName
|
||||
return nil
|
||||
})
|
||||
|
||||
@@ -102,6 +108,7 @@ func NewTelegram(params TelegramParams) (*Telegram, error) {
|
||||
// Send to telegram recipients
|
||||
func (t *Telegram) Send(ctx context.Context, req Request) error {
|
||||
log.Printf("[DEBUG] send telegram notification for comment ID %s", req.Comment.ID)
|
||||
result := new(multierror.Error)
|
||||
|
||||
msg, err := buildTelegramMessage(req)
|
||||
if err != nil {
|
||||
@@ -110,10 +117,20 @@ func (t *Telegram) Send(ctx context.Context, req Request) error {
|
||||
|
||||
if t.AdminChannelID != "" {
|
||||
err := t.sendMessage(ctx, msg, t.AdminChannelID)
|
||||
return errors.Wrapf(err,
|
||||
"problem sending admin telegram notification about comment ID %s to %s", req.Comment.ID, t.AdminChannelID)
|
||||
result = multierror.Append(errors.Wrapf(err,
|
||||
"problem sending admin telegram notification about comment ID %s to %s", req.Comment.ID, t.AdminChannelID),
|
||||
)
|
||||
}
|
||||
return nil
|
||||
|
||||
if t.UserNotifications {
|
||||
for _, user := range req.Telegrams {
|
||||
err := t.sendMessage(ctx, msg, user)
|
||||
result = multierror.Append(errors.Wrapf(err,
|
||||
"problem sending user telegram notification about comment ID %s to %q", req.Comment.ID, user),
|
||||
)
|
||||
}
|
||||
}
|
||||
return result.ErrorOrNil()
|
||||
}
|
||||
|
||||
func (t *Telegram) sendMessage(ctx context.Context, b []byte, chatID string) error {
|
||||
@@ -205,9 +222,48 @@ func escapeText(title string) string {
|
||||
return res
|
||||
}
|
||||
|
||||
// SendVerification is not implemented for telegram
|
||||
func (t *Telegram) SendVerification(_ context.Context, _ VerificationRequest) error {
|
||||
return nil
|
||||
func escapeCode(text string) string {
|
||||
escSymbols := []string{"`", `\`}
|
||||
res := text
|
||||
for _, esc := range escSymbols {
|
||||
res = strings.Replace(res, esc, "\\"+esc, -1)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// SendVerification sends user verification message to the specified user
|
||||
func (t *Telegram) SendVerification(ctx context.Context, req VerificationRequest) error {
|
||||
if req.Telegram == "" {
|
||||
// this means we can't send this request via Telegram
|
||||
return nil
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return errors.Errorf("sending message to %q aborted due to canceled context", req.User)
|
||||
default:
|
||||
}
|
||||
|
||||
log.Printf("[DEBUG] send verification via %s, user %s", t, req.User)
|
||||
msg, err := t.buildVerificationMessage(req.User, req.Token, req.SiteID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return t.sendMessage(ctx, msg, req.Telegram)
|
||||
}
|
||||
|
||||
// buildVerificationMessage generates verification telegram message based on given input
|
||||
func (t *Telegram) buildVerificationMessage(user, token, site string) ([]byte, error) {
|
||||
result := fmt.Sprintf("This is confirmation for %s on site %s\n"+
|
||||
"Please copy and paste this text into “token” field on comments page to confirm subscription:\n\n\n"+
|
||||
"```%s```",
|
||||
escapeText(user), escapeText(site), escapeCode(token))
|
||||
body := telegramMsg{Text: result, ParseMode: "MarkdownV2"}
|
||||
b, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (t *Telegram) String() string {
|
||||
@@ -215,5 +271,8 @@ func (t *Telegram) String() string {
|
||||
if t.AdminChannelID != "" {
|
||||
result += " with admin notifications to " + t.AdminChannelID
|
||||
}
|
||||
if t.UserNotifications {
|
||||
result += " with user notifications enabled"
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -97,6 +97,7 @@ func TestTelegram_Send(t *testing.T) {
|
||||
tb, err := NewTelegram(TelegramParams{
|
||||
AdminChannelID: "remark_test",
|
||||
Token: "good-token",
|
||||
UserNotifications: true,
|
||||
apiPrefix: ts.URL + "/",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
@@ -106,7 +107,7 @@ func TestTelegram_Send(t *testing.T) {
|
||||
cp := store.Comment{Text: "some parent text"}
|
||||
cp.User.Name = "to"
|
||||
|
||||
err = tb.Send(context.TODO(), Request{Comment: c, parent: cp})
|
||||
err = tb.Send(context.TODO(), Request{Comment: c, parent: cp, Telegrams: []string{"test_user_channel"}})
|
||||
assert.NoError(t, err)
|
||||
c.PostTitle = "test title"
|
||||
err = tb.Send(context.TODO(), Request{Comment: c, parent: cp})
|
||||
@@ -119,20 +120,21 @@ func TestTelegram_Send(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
tb, err = NewTelegram(TelegramParams{
|
||||
AdminChannelID: "remark_test",
|
||||
Token: "non-json-resp",
|
||||
apiPrefix: ts.URL + "/",
|
||||
AdminChannelID: "remark_test",
|
||||
Token: "non-json-resp",
|
||||
UserNotifications: true,
|
||||
apiPrefix: ts.URL + "/",
|
||||
})
|
||||
assert.Error(t, err, "should fail")
|
||||
err = tb.Send(context.TODO(), Request{Comment: c, parent: cp})
|
||||
err = tb.Send(context.TODO(), Request{Comment: c, parent: cp, Telegrams: []string{"test_user_channel"}})
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "unexpected telegram API status code 404", "send on broken tg")
|
||||
|
||||
assert.Equal(t, "telegram with admin notifications to remark_test", tb.String())
|
||||
assert.Equal(t, "telegram with admin notifications to remark_test with user notifications enabled", tb.String())
|
||||
|
||||
// bad API URL
|
||||
tb.apiPrefix = "http://non-existent"
|
||||
err = tb.Send(context.TODO(), Request{Comment: c, parent: cp})
|
||||
err = tb.Send(context.TODO(), Request{Comment: c, parent: cp, Telegrams: []string{"test_user_channel"}})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
@@ -148,8 +150,28 @@ func TestTelegram_SendVerification(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, tb)
|
||||
|
||||
err = tb.SendVerification(context.TODO(), VerificationRequest{})
|
||||
// proper VerificationRequest without telegram
|
||||
req := VerificationRequest{
|
||||
SiteID: "remark",
|
||||
User: "test_username",
|
||||
Token: "secret_",
|
||||
}
|
||||
assert.NoError(t, tb.SendVerification(context.TODO(), req))
|
||||
|
||||
// proper VerificationRequest with telegram
|
||||
req.Telegram = "test"
|
||||
assert.NoError(t, tb.SendVerification(context.TODO(), req))
|
||||
|
||||
// VerificationRequest with canceled context
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
cancel()
|
||||
assert.EqualError(t, tb.SendVerification(ctx, req), "sending message to \"test_username\" aborted due to canceled context")
|
||||
|
||||
// test buildVerificationMessage separately for message text
|
||||
res, err := tb.buildVerificationMessage(req.User, req.Token, req.SiteID)
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, string(res), "This is confirmation for test\\\\_username on site remark")
|
||||
assert.Contains(t, string(res), `secret_`)
|
||||
}
|
||||
|
||||
func mockTelegramServer() *httptest.Server {
|
||||
|
||||
@@ -55,13 +55,14 @@ type Rest struct {
|
||||
Low int
|
||||
Critical int
|
||||
}
|
||||
UpdateLimiter float64
|
||||
EmailNotifications bool
|
||||
EmojiEnabled bool
|
||||
SimpleView bool
|
||||
ProxyCORS bool
|
||||
SendJWTHeader bool
|
||||
AllowedAncestors []string // sets Content-Security-Policy "frame-ancestors ..."
|
||||
UpdateLimiter float64
|
||||
EmailNotifications bool
|
||||
TelegramBotUsername string
|
||||
EmojiEnabled bool
|
||||
SimpleView bool
|
||||
ProxyCORS bool
|
||||
SendJWTHeader bool
|
||||
AllowedAncestors []string // sets Content-Security-Policy "frame-ancestors ..."
|
||||
|
||||
SSLConfig SSLConfig
|
||||
httpsServer *http.Server
|
||||
@@ -322,6 +323,9 @@ func (s *Rest) routes() chi.Router {
|
||||
rauth.With(rejectAnonUser).Post("/email/subscribe", s.privRest.sendEmailConfirmationCtrl)
|
||||
rauth.With(rejectAnonUser).Post("/email/confirm", s.privRest.setConfirmedEmailCtrl)
|
||||
rauth.With(rejectAnonUser).Delete("/email", s.privRest.deleteEmailCtrl)
|
||||
rauth.With(rejectAnonUser).Post("/telegram/subscribe", s.privRest.sendTelegramConfirmationCtrl)
|
||||
rauth.With(rejectAnonUser).Post("/telegram/confirm", s.privRest.setConfirmedTelegramCtrl)
|
||||
rauth.With(rejectAnonUser).Delete("/telegram", s.privRest.deleteTelegramCtrl)
|
||||
})
|
||||
|
||||
// protected routes, anonymous rejected
|
||||
@@ -407,40 +411,42 @@ func (s *Rest) configCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
emails, _ := s.DataService.AdminStore.Email(siteID)
|
||||
|
||||
cnf := struct {
|
||||
Version string `json:"version"`
|
||||
EditDuration int `json:"edit_duration"`
|
||||
AdminEdit bool `json:"admin_edit"`
|
||||
MaxCommentSize int `json:"max_comment_size"`
|
||||
Admins []string `json:"admins"`
|
||||
AdminEmail string `json:"admin_email"`
|
||||
Auth []string `json:"auth_providers"`
|
||||
AnonVote bool `json:"anon_vote"`
|
||||
LowScore int `json:"low_score"`
|
||||
CriticalScore int `json:"critical_score"`
|
||||
PositiveScore bool `json:"positive_score"`
|
||||
ReadOnlyAge int `json:"readonly_age"`
|
||||
MaxImageSize int `json:"max_image_size"`
|
||||
EmailNotifications bool `json:"email_notifications"`
|
||||
EmojiEnabled bool `json:"emoji_enabled"`
|
||||
SimpleView bool `json:"simple_view"`
|
||||
SendJWTHeader bool `json:"send_jwt_header"`
|
||||
Version string `json:"version"`
|
||||
EditDuration int `json:"edit_duration"`
|
||||
AdminEdit bool `json:"admin_edit"`
|
||||
MaxCommentSize int `json:"max_comment_size"`
|
||||
Admins []string `json:"admins"`
|
||||
AdminEmail string `json:"admin_email"`
|
||||
Auth []string `json:"auth_providers"`
|
||||
AnonVote bool `json:"anon_vote"`
|
||||
LowScore int `json:"low_score"`
|
||||
CriticalScore int `json:"critical_score"`
|
||||
PositiveScore bool `json:"positive_score"`
|
||||
ReadOnlyAge int `json:"readonly_age"`
|
||||
MaxImageSize int `json:"max_image_size"`
|
||||
EmailNotifications bool `json:"email_notifications"`
|
||||
TelegramBotUsername string `json:"telegram_bot_username"`
|
||||
EmojiEnabled bool `json:"emoji_enabled"`
|
||||
SimpleView bool `json:"simple_view"`
|
||||
SendJWTHeader bool `json:"send_jwt_header"`
|
||||
}{
|
||||
Version: s.Version,
|
||||
EditDuration: int(s.DataService.EditDuration.Seconds()),
|
||||
AdminEdit: s.DataService.AdminEdits,
|
||||
MaxCommentSize: s.DataService.MaxCommentSize,
|
||||
Admins: admins,
|
||||
AdminEmail: emails,
|
||||
LowScore: s.ScoreThresholds.Low,
|
||||
CriticalScore: s.ScoreThresholds.Critical,
|
||||
PositiveScore: s.DataService.PositiveScore,
|
||||
ReadOnlyAge: s.ReadOnlyAge,
|
||||
MaxImageSize: s.ImageService.MaxSize,
|
||||
EmailNotifications: s.EmailNotifications,
|
||||
EmojiEnabled: s.EmojiEnabled,
|
||||
AnonVote: s.AnonVote,
|
||||
SimpleView: s.SimpleView,
|
||||
SendJWTHeader: s.SendJWTHeader,
|
||||
Version: s.Version,
|
||||
EditDuration: int(s.DataService.EditDuration.Seconds()),
|
||||
AdminEdit: s.DataService.AdminEdits,
|
||||
MaxCommentSize: s.DataService.MaxCommentSize,
|
||||
Admins: admins,
|
||||
AdminEmail: emails,
|
||||
LowScore: s.ScoreThresholds.Low,
|
||||
CriticalScore: s.ScoreThresholds.Critical,
|
||||
PositiveScore: s.DataService.PositiveScore,
|
||||
ReadOnlyAge: s.ReadOnlyAge,
|
||||
MaxImageSize: s.ImageService.MaxSize,
|
||||
EmailNotifications: s.EmailNotifications,
|
||||
TelegramBotUsername: s.TelegramBotUsername,
|
||||
EmojiEnabled: s.EmojiEnabled,
|
||||
AnonVote: s.AnonVote,
|
||||
SimpleView: s.SimpleView,
|
||||
SendJWTHeader: s.SendJWTHeader,
|
||||
}
|
||||
|
||||
cnf.Auth = []string{}
|
||||
|
||||
@@ -52,6 +52,8 @@ type privStore interface {
|
||||
User(siteID, userID string, limit, skip int, user store.User) ([]store.Comment, error)
|
||||
GetUserEmail(siteID string, userID string) (string, error)
|
||||
SetUserEmail(siteID string, userID string, value string) (string, error)
|
||||
GetUserTelegram(siteID string, userID string) (string, error)
|
||||
SetUserTelegram(siteID string, userID string, value string) (string, error)
|
||||
DeleteUserDetail(siteID string, userID string, detail engine.UserDetail) error
|
||||
ValidateComment(c *store.Comment) error
|
||||
IsVerified(siteID string, userID string) bool
|
||||
@@ -268,6 +270,7 @@ func (s *private) getEmailCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// sendEmailConfirmationCtrl gets address and siteID from query, makes confirmation token and sends it to user.
|
||||
// GET /email/subscribe?site=siteID&address=someone@example.com
|
||||
//nolint:dupl // too hard to deduplicate that logic, as then it's tricky to use SendErrorJSON
|
||||
func (s *private) sendEmailConfirmationCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
user := rest.MustGetUserInfo(r)
|
||||
address := r.URL.Query().Get("address")
|
||||
@@ -314,6 +317,55 @@ func (s *private) sendEmailConfirmationCtrl(w http.ResponseWriter, r *http.Reque
|
||||
render.JSON(w, r, R.JSON{"user": user, "address": address})
|
||||
}
|
||||
|
||||
// sendTelegramConfirmationCtrl gets address and siteID from query, makes confirmation token and sends it to user.
|
||||
// GET /telegram/subscribe?site=siteID&address=@someone
|
||||
//nolint:dupl // too hard to deduplicate that logic, as then it's tricky to use SendErrorJSON
|
||||
func (s *private) sendTelegramConfirmationCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
user := rest.MustGetUserInfo(r)
|
||||
address := r.URL.Query().Get("address")
|
||||
siteID := r.URL.Query().Get("site")
|
||||
if address == "" {
|
||||
rest.SendErrorJSON(w, r, http.StatusBadRequest,
|
||||
errors.New("missing parameter"), "address parameter is required", rest.ErrInternal)
|
||||
return
|
||||
}
|
||||
existingAddress, err := s.dataService.GetUserTelegram(siteID, user.ID)
|
||||
if err != nil {
|
||||
log.Printf("[WARN] can't read telegram for %s, %v", user.ID, err)
|
||||
}
|
||||
if address == existingAddress {
|
||||
rest.SendErrorJSON(w, r, http.StatusConflict,
|
||||
errors.New("already verified"), "telegram address is already verified for this user", rest.ErrInternal)
|
||||
return
|
||||
}
|
||||
claims := token.Claims{
|
||||
Handshake: &token.Handshake{ID: user.ID + "::" + address},
|
||||
StandardClaims: jwt.StandardClaims{
|
||||
Audience: r.URL.Query().Get("site"),
|
||||
ExpiresAt: time.Now().Add(30 * time.Minute).Unix(),
|
||||
NotBefore: time.Now().Add(-1 * time.Minute).Unix(),
|
||||
Issuer: "remark42",
|
||||
},
|
||||
}
|
||||
|
||||
tkn, err := s.authenticator.TokenService().Token(claims)
|
||||
if err != nil {
|
||||
rest.SendErrorJSON(w, r, http.StatusForbidden, err, "failed to make verification token", rest.ErrInternal)
|
||||
return
|
||||
}
|
||||
|
||||
s.notifyService.SubmitVerification(
|
||||
notify.VerificationRequest{
|
||||
SiteID: siteID,
|
||||
User: user.Name,
|
||||
Telegram: address,
|
||||
Token: tkn,
|
||||
},
|
||||
)
|
||||
|
||||
render.JSON(w, r, R.JSON{"user": user, "address": address})
|
||||
}
|
||||
|
||||
// setConfirmedEmailCtrl uses provided token parameter (generated by sendEmailConfirmationCtrl) to set email and add it to user token
|
||||
// PUT /email/confirm?site=siteID&tkn=jwt
|
||||
func (s *private) setConfirmedEmailCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -366,6 +418,46 @@ func (s *private) setConfirmedEmailCtrl(w http.ResponseWriter, r *http.Request)
|
||||
render.JSON(w, r, R.JSON{"updated": true, "address": val})
|
||||
}
|
||||
|
||||
// setConfirmedTelegramCtrl uses provided token parameter (generated by sendTelegramConfirmationCtrl) to set telegram and add it to user token
|
||||
// PUT /telegram/confirm?site=siteID&tkn=jwt
|
||||
func (s *private) setConfirmedTelegramCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
tkn := r.URL.Query().Get("tkn")
|
||||
if tkn == "" {
|
||||
rest.SendErrorJSON(w, r, http.StatusBadRequest, errors.New("missing parameter"), "token parameter is required", rest.ErrInternal)
|
||||
return
|
||||
}
|
||||
user := rest.MustGetUserInfo(r)
|
||||
siteID := r.URL.Query().Get("site")
|
||||
|
||||
confClaims, err := s.authenticator.TokenService().Parse(tkn)
|
||||
if err != nil {
|
||||
rest.SendErrorJSON(w, r, http.StatusForbidden, err, "failed to verify confirmation token", rest.ErrInternal)
|
||||
return
|
||||
}
|
||||
|
||||
if s.authenticator.TokenService().IsExpired(confClaims) {
|
||||
rest.SendErrorJSON(w, r, http.StatusForbidden, errors.New("expired"), "failed to verify confirmation token", rest.ErrInternal)
|
||||
return
|
||||
}
|
||||
|
||||
elems := strings.Split(confClaims.Handshake.ID, "::")
|
||||
if len(elems) != 2 || elems[0] != user.ID {
|
||||
rest.SendErrorJSON(w, r, http.StatusBadRequest, errors.New(confClaims.Handshake.ID), "invalid handshake token", rest.ErrInternal)
|
||||
return
|
||||
}
|
||||
address := elems[1]
|
||||
|
||||
log.Printf("[DEBUG] set telegram for user %s", user.ID)
|
||||
|
||||
val, err := s.dataService.SetUserTelegram(siteID, user.ID, address)
|
||||
if err != nil {
|
||||
code := parseError(err, rest.ErrInternal)
|
||||
rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't set telegram for user", code)
|
||||
return
|
||||
}
|
||||
render.JSON(w, r, R.JSON{"updated": true, "address": val})
|
||||
}
|
||||
|
||||
// POST/GET /email/unsubscribe.html?site=siteID&tkn=jwt - unsubscribe the user in token from email notifications
|
||||
func (s *private) emailUnsubscribeCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
tkn := r.URL.Query().Get("tkn")
|
||||
@@ -480,6 +572,20 @@ func (s *private) deleteEmailCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
render.JSON(w, r, R.JSON{"deleted": true})
|
||||
}
|
||||
|
||||
// DELETE /telegram?site=siteID - removes user's telegram
|
||||
func (s *private) deleteTelegramCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
user := rest.MustGetUserInfo(r)
|
||||
siteID := r.URL.Query().Get("site")
|
||||
log.Printf("[DEBUG] remove telegram for user %s", user.ID)
|
||||
|
||||
if err := s.dataService.DeleteUserDetail(siteID, user.ID, engine.UserTelegram); err != nil {
|
||||
code := parseError(err, rest.ErrInternal)
|
||||
rest.SendErrorJSON(w, r, http.StatusBadRequest, err, "can't delete telegram for user", code)
|
||||
return
|
||||
}
|
||||
render.JSON(w, r, R.JSON{"deleted": true})
|
||||
}
|
||||
|
||||
// GET /userdata?site=siteID - exports all data about the user as a json with user info and list of all comments
|
||||
func (s *private) userAllDataCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
siteID := r.URL.Query().Get("site")
|
||||
|
||||
@@ -573,7 +573,7 @@ func (fs *MockFS) ReadFile(path string) ([]byte, error) {
|
||||
return []byte(fmt.Sprintf("template %s", path)), nil
|
||||
}
|
||||
|
||||
func TestRest_Email(t *testing.T) {
|
||||
func TestRest_EmailAndTelegram(t *testing.T) {
|
||||
ts, srv, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
@@ -617,6 +617,17 @@ func TestRest_Email(t *testing.T) {
|
||||
{description: "unsubscribe user, wrong token", url: "/email/unsubscribe.html?site=remark42&tkn=jwt", method: http.MethodGet, responseCode: http.StatusForbidden},
|
||||
{description: "unsubscribe user, good token", url: fmt.Sprintf("/email/unsubscribe.html?site=remark42&tkn=%s", goodToken), method: http.MethodPost, responseCode: http.StatusOK},
|
||||
{description: "unsubscribe user second time, good token", url: fmt.Sprintf("/email/unsubscribe.html?site=remark42&tkn=%s", goodToken), method: http.MethodPost, responseCode: http.StatusConflict},
|
||||
{description: "issue delete request without auth", url: "/api/v1/telegram", method: http.MethodDelete, responseCode: http.StatusUnauthorized, noAuth: true},
|
||||
{description: "issue delete request without site_id", url: "/api/v1/telegram", method: http.MethodDelete, responseCode: http.StatusBadRequest},
|
||||
{description: "delete non-existent user telegram", url: "/api/v1/telegram?site=remark42", method: http.MethodDelete, responseCode: http.StatusOK},
|
||||
{description: "set user telegram, token not set", url: "/api/v1/telegram/confirm?site=remark42", method: http.MethodPost, responseCode: http.StatusBadRequest},
|
||||
{description: "send confirmation without address", url: "/api/v1/telegram/subscribe?site=remark42", method: http.MethodPost, responseCode: http.StatusBadRequest},
|
||||
{description: "send confirmation", url: "/api/v1/telegram/subscribe?site=remark42&address=good@example.com", method: http.MethodPost, responseCode: http.StatusOK},
|
||||
{description: "set user telegram, token is good", url: fmt.Sprintf("/api/v1/telegram/confirm?site=remark42&tkn=%s", goodToken), method: http.MethodPost, responseCode: http.StatusOK},
|
||||
{description: "send confirmation with same address", url: "/api/v1/telegram/subscribe?site=remark42&address=good@example.com", method: http.MethodPost, responseCode: http.StatusConflict},
|
||||
{description: "delete user telegram", url: "/api/v1/telegram?site=remark42", method: http.MethodDelete, responseCode: http.StatusOK},
|
||||
{description: "send another confirmation", url: "/api/v1/telegram/subscribe?site=remark42&address=good@example.com", method: http.MethodPost, responseCode: http.StatusOK},
|
||||
{description: "set user telegram, token is good", url: fmt.Sprintf("/api/v1/telegram/confirm?site=remark42&tkn=%s", goodToken), method: http.MethodPost, responseCode: http.StatusOK},
|
||||
}
|
||||
client := http.Client{}
|
||||
for _, x := range testData {
|
||||
@@ -645,7 +656,7 @@ func TestRest_Email(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRest_EmailNotification(t *testing.T) {
|
||||
func TestRest_EmailAndTelegramNotification(t *testing.T) {
|
||||
ts, srv, teardown := startupT(t)
|
||||
defer teardown()
|
||||
|
||||
@@ -675,8 +686,9 @@ func TestRest_EmailNotification(t *testing.T) {
|
||||
time.Sleep(time.Millisecond * 30)
|
||||
require.Equal(t, 1, len(mockDestination.Get()))
|
||||
assert.Empty(t, mockDestination.Get()[0].Emails)
|
||||
assert.Empty(t, mockDestination.Get()[0].Telegrams)
|
||||
|
||||
// create child comment from another user, email notification only to admin expected
|
||||
// create child comment from another user, email and telegram notification only to admin expected
|
||||
req, err = http.NewRequest("POST", ts.URL+"/api/v1/comment", strings.NewReader(fmt.Sprintf(
|
||||
`{"text": "test 456",
|
||||
"pid": "%s",
|
||||
@@ -695,6 +707,7 @@ func TestRest_EmailNotification(t *testing.T) {
|
||||
time.Sleep(time.Millisecond * 30)
|
||||
require.Equal(t, 2, len(mockDestination.Get()))
|
||||
assert.Empty(t, mockDestination.Get()[1].Emails)
|
||||
assert.Empty(t, mockDestination.Get()[1].Telegrams)
|
||||
|
||||
// send confirmation token for email
|
||||
req, err = http.NewRequest(http.MethodPost, ts.URL+"/api/v1/email/subscribe?site=remark42&address=good@example.com", nil)
|
||||
@@ -710,10 +723,37 @@ func TestRest_EmailNotification(t *testing.T) {
|
||||
time.Sleep(time.Millisecond * 30)
|
||||
require.Equal(t, 1, len(mockDestination.GetVerify()))
|
||||
assert.Equal(t, "good@example.com", mockDestination.GetVerify()[0].Email)
|
||||
verificationToken := mockDestination.GetVerify()[0].Token
|
||||
emailVerificationToken := mockDestination.GetVerify()[0].Token
|
||||
|
||||
// verify email
|
||||
req, err = http.NewRequest(http.MethodPost, ts.URL+fmt.Sprintf("/api/v1/email/confirm?site=remark42&tkn=%s", verificationToken), nil)
|
||||
req, err = http.NewRequest(http.MethodPost, ts.URL+fmt.Sprintf("/api/v1/email/confirm?site=remark42&tkn=%s", emailVerificationToken), nil)
|
||||
require.NoError(t, err)
|
||||
req.Header.Add("X-JWT", devToken)
|
||||
resp, err = client.Do(req)
|
||||
require.NoError(t, err)
|
||||
body, err = ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, resp.Body.Close())
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode, string(body))
|
||||
|
||||
// send confirmation token for telegram
|
||||
req, err = http.NewRequest(http.MethodPost, ts.URL+"/api/v1/telegram/subscribe?site=remark42&address=good_telegram", nil)
|
||||
require.NoError(t, err)
|
||||
req.Header.Add("X-JWT", devToken)
|
||||
resp, err = client.Do(req)
|
||||
require.NoError(t, err)
|
||||
body, err = ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, resp.Body.Close())
|
||||
require.Equal(t, http.StatusOK, resp.StatusCode, string(body))
|
||||
// wait for mock notification Submit to kick off
|
||||
time.Sleep(time.Millisecond * 30)
|
||||
require.Equal(t, 2, len(mockDestination.GetVerify()))
|
||||
assert.Equal(t, "good_telegram", mockDestination.GetVerify()[1].Telegram)
|
||||
telegramVerificationToken := mockDestination.GetVerify()[1].Token
|
||||
|
||||
// verify telegram
|
||||
req, err = http.NewRequest(http.MethodPost, ts.URL+fmt.Sprintf("/api/v1/telegram/confirm?site=remark42&tkn=%s", telegramVerificationToken), nil)
|
||||
require.NoError(t, err)
|
||||
req.Header.Add("X-JWT", devToken)
|
||||
resp, err = client.Do(req)
|
||||
@@ -739,7 +779,7 @@ func TestRest_EmailNotification(t *testing.T) {
|
||||
assert.Equal(t, store.User{Name: "developer one", ID: "dev", EmailSubscription: true,
|
||||
Picture: "http://example.com/pic.png", IP: "127.0.0.1", SiteID: "remark42"}, user)
|
||||
|
||||
// create child comment from another user, email notification expected
|
||||
// create child comment from another user, email and telegram notification expected
|
||||
req, err = http.NewRequest("POST", ts.URL+"/api/v1/comment", strings.NewReader(fmt.Sprintf(
|
||||
`{"text": "test 789",
|
||||
"pid": "%s",
|
||||
@@ -758,6 +798,7 @@ func TestRest_EmailNotification(t *testing.T) {
|
||||
time.Sleep(time.Millisecond * 30)
|
||||
require.Equal(t, 3, len(mockDestination.Get()))
|
||||
assert.Equal(t, []string{"good@example.com"}, mockDestination.Get()[2].Emails)
|
||||
assert.Equal(t, []string{"good_telegram"}, mockDestination.Get()[2].Telegrams)
|
||||
|
||||
// delete user's email
|
||||
req, err = http.NewRequest(http.MethodDelete, ts.URL+"/api/v1/email?site=remark42", nil)
|
||||
@@ -770,7 +811,18 @@ func TestRest_EmailNotification(t *testing.T) {
|
||||
require.NoError(t, resp.Body.Close())
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode, string(body))
|
||||
|
||||
// create child comment from another user, no email notification
|
||||
// delete user's telegram
|
||||
req, err = http.NewRequest(http.MethodDelete, ts.URL+"/api/v1/telegram?site=remark42", nil)
|
||||
require.NoError(t, err)
|
||||
req.Header.Add("X-JWT", devToken)
|
||||
resp, err = client.Do(req)
|
||||
require.NoError(t, err)
|
||||
body, err = ioutil.ReadAll(resp.Body)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, resp.Body.Close())
|
||||
assert.Equal(t, http.StatusOK, resp.StatusCode, string(body))
|
||||
|
||||
// create child comment from another user, no email or telegram notification
|
||||
req, err = http.NewRequest("POST", ts.URL+"/api/v1/comment", strings.NewReader(
|
||||
`{"text": "test 321",
|
||||
"user": {"name": "other_user"},
|
||||
@@ -788,6 +840,7 @@ func TestRest_EmailNotification(t *testing.T) {
|
||||
time.Sleep(time.Millisecond * 30)
|
||||
require.Equal(t, 4, len(mockDestination.Get()))
|
||||
assert.Empty(t, mockDestination.Get()[3].Emails)
|
||||
assert.Empty(t, mockDestination.Get()[3].Telegrams)
|
||||
}
|
||||
|
||||
func TestRest_UserAllData(t *testing.T) {
|
||||
|
||||
@@ -134,6 +134,18 @@ X-JWT: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJyZW1hcmsiLCJleHAiOjE5NzYw
|
||||
DELETE {{host}}/api/v1/email?site={{site}}
|
||||
X-JWT: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJyZW1hcmsiLCJleHAiOjE5NzYwNTY3NTYsImp0aSI6IjJlOGJmMTE5OTI0MjQxMDRjYjFhZGRlODllMWYwNGFiMTg4YWZjMzQiLCJpYXQiOjE1NzYwNTY0NTYsImlzcyI6InJlbWFyazQyIiwidXNlciI6eyJuYW1lIjoiZGV2X3VzZXIiLCJpZCI6ImRldl91c2VyIiwicGljdHVyZSI6Imh0dHA6Ly8xMjcuMC4wLjE6ODA4MC9hcGkvdjEvYXZhdGFyL2NjZmEyYWJkMDE2Njc2MDViNGUxZmM0ZmNiOTFiMWUxYWYzMjMyNDAuaW1hZ2UiLCJhdHRycyI6eyJhZG1pbiI6dHJ1ZSwiYmxvY2tlZCI6ZmFsc2V9fX0.6Qt5s2enBMRC-Jmsua01yViVYI95Dx6BPBMaNjj36d4
|
||||
|
||||
### send confirmation token for current user to specified telegram. auth token for dev user for secret=12345.
|
||||
POST {{host}}/api/v1/telegram/subscribe?site={{site}}&address={{telegram}}
|
||||
X-JWT: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJyZW1hcmsiLCJleHAiOjE5NzYwNTY3NTYsImp0aSI6IjJlOGJmMTE5OTI0MjQxMDRjYjFhZGRlODllMWYwNGFiMTg4YWZjMzQiLCJpYXQiOjE1NzYwNTY0NTYsImlzcyI6InJlbWFyazQyIiwidXNlciI6eyJuYW1lIjoiZGV2X3VzZXIiLCJpZCI6ImRldl91c2VyIiwicGljdHVyZSI6Imh0dHA6Ly8xMjcuMC4wLjE6ODA4MC9hcGkvdjEvYXZhdGFyL2NjZmEyYWJkMDE2Njc2MDViNGUxZmM0ZmNiOTFiMWUxYWYzMjMyNDAuaW1hZ2UiLCJhdHRycyI6eyJhZG1pbiI6dHJ1ZSwiYmxvY2tlZCI6ZmFsc2V9fX0.6Qt5s2enBMRC-Jmsua01yViVYI95Dx6BPBMaNjj36d4
|
||||
|
||||
### add telegram for notifications for current user via token from telegram. auth token for dev user for secret=12345.
|
||||
POST {{host}}/api/v1/telegram/confirm?site={{site}}&tkn={{token}}
|
||||
X-JWT: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJyZW1hcmsiLCJleHAiOjE5NzYwNTY3NTYsImp0aSI6IjJlOGJmMTE5OTI0MjQxMDRjYjFhZGRlODllMWYwNGFiMTg4YWZjMzQiLCJpYXQiOjE1NzYwNTY0NTYsImlzcyI6InJlbWFyazQyIiwidXNlciI6eyJuYW1lIjoiZGV2X3VzZXIiLCJpZCI6ImRldl91c2VyIiwicGljdHVyZSI6Imh0dHA6Ly8xMjcuMC4wLjE6ODA4MC9hcGkvdjEvYXZhdGFyL2NjZmEyYWJkMDE2Njc2MDViNGUxZmM0ZmNiOTFiMWUxYWYzMjMyNDAuaW1hZ2UiLCJhdHRycyI6eyJhZG1pbiI6dHJ1ZSwiYmxvY2tlZCI6ZmFsc2V9fX0.6Qt5s2enBMRC-Jmsua01yViVYI95Dx6BPBMaNjj36d4
|
||||
|
||||
### delete current user telegram. auth token for dev user for secret=12345.
|
||||
DELETE {{host}}/api/v1/telegram?site={{site}}
|
||||
X-JWT: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJyZW1hcmsiLCJleHAiOjE5NzYwNTY3NTYsImp0aSI6IjJlOGJmMTE5OTI0MjQxMDRjYjFhZGRlODllMWYwNGFiMTg4YWZjMzQiLCJpYXQiOjE1NzYwNTY0NTYsImlzcyI6InJlbWFyazQyIiwidXNlciI6eyJuYW1lIjoiZGV2X3VzZXIiLCJpZCI6ImRldl91c2VyIiwicGljdHVyZSI6Imh0dHA6Ly8xMjcuMC4wLjE6ODA4MC9hcGkvdjEvYXZhdGFyL2NjZmEyYWJkMDE2Njc2MDViNGUxZmM0ZmNiOTFiMWUxYWYzMjMyNDAuaW1hZ2UiLCJhdHRycyI6eyJhZG1pbiI6dHJ1ZSwiYmxvY2tlZCI6ZmFsc2V9fX0.6Qt5s2enBMRC-Jmsua01yViVYI95Dx6BPBMaNjj36d4
|
||||
|
||||
### get config
|
||||
GET {{host}}/api/v1/config?site={{site}}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ beforeEach(() => {
|
||||
simple_view: false,
|
||||
anon_vote: false,
|
||||
email_notifications: false,
|
||||
telegram_bot_username: "",
|
||||
emoji_enabled: true,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -29,6 +29,7 @@ export const StaticStore: StaticStoreType = {
|
||||
simple_view: false,
|
||||
anon_vote: false,
|
||||
email_notifications: false,
|
||||
telegram_bot_username: "",
|
||||
emoji_enabled: false,
|
||||
},
|
||||
query: querySettings as QuerySettingsType,
|
||||
|
||||
@@ -114,6 +114,7 @@ export interface Config {
|
||||
simple_view: boolean;
|
||||
anon_vote: boolean;
|
||||
email_notifications: boolean;
|
||||
telegram_bot_username: string;
|
||||
emoji_enabled: boolean;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user