Skip to content

Commit

Permalink
Merge pull request #15 from ueckoken/token-with-white
Browse files Browse the repository at this point in the history
  • Loading branch information
Azuki-bar authored Jan 1, 2023
2 parents b1996b8 + e4fbf7c commit d44b2c1
Show file tree
Hide file tree
Showing 9 changed files with 198 additions and 38 deletions.
13 changes: 13 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
工事中です。

## リリース方法

最新のコミットに対してタグを貼ってください。(自動化したいです)
そしてそのタグをプッシュすると自動的にリリースノートが生成されます。
Assets として(Win, Mac, Linux) x (x86_64, arm64, i386)のビルド成果物が生成されます。
また ghcr.io にコンテナイメージがプッシュされます。これは x86_64 と arm64 のみに対応です。

```bash
git tag v(SEM_VER)
git push --tags
```
46 changes: 28 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,21 +16,31 @@ this application is OTP(One Time Password) client works as Discord app.

以下の環境変数を設定してください。

| キー ||
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `DISCORD_APP_TOKEN` | Discord の Bot トークン |
| `DISCORD_GUILD_ID` | Discord の GuildID です。 |
| `ALLOWRD_REPLY_CHANNEL_IDS` | メッセージの返答を許可するチャンネルの ID です。分割文字は`,`で複数設定できます |
| `TOTP_TOKENS` | TOTP トークンです。`サービス名1:トークン,サービス名2:トークン`のように指定してください。「QR コードを読み取れない」のような場所をクリックすると取得できます |

### リリース方法

最新のコミットに対してタグを貼ってください。(自動化したいです)
そしてそのタグをプッシュすると自動的にリリースノートが生成されます。
Assets として(Win, Mac, Linux) x (x86_64, arm64, i386)のビルド成果物が生成されます。
また ghcr.io にコンテナイメージがプッシュされます。これは x86_64 と arm64 のみに対応です。

```bash
git tag v(SEM_VER)
git push --tags
```
#### `DISCORD_APP_TOKEN`

(required)

Discord の Bot トークンを指定します。

#### `DISCORD_GUILD_ID`

(required)

メッセージを返答する Discord の GuildID を指定します。

#### `ALLOWED_REPLY_CHANNEL_IDS`

(required)

メッセージの返答を許可するチャンネルの ID です。分割文字を`,`として複数設定できます。

#### `TOTP_TOKENS`

(required)
TOTP トークンです。`サービス名1:トークン,サービス名2:トークン`のように指定してください。トークンの間にあるスペースは入れても入れなくても問題ないです。「QR コードを読み取れない」のような場所をクリックすると取得できます

#### `IS_DEVELOPMENT`

(optional)
`1`を指定すると開発モードになって、ログが json 形式ではなくなります。

32 changes: 19 additions & 13 deletions discord.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"fmt"
"strings"
"time"

"github.com/bwmarrin/discordgo"
Expand All @@ -15,6 +16,13 @@ type TotpHandler struct {
config Config
}

func NewTotpHandler(logger *zap.Logger, config Config) *TotpHandler {
return &TotpHandler{
logger: logger,
config: config,
}
}

func (h *TotpHandler) CreateTotpApplicationCommand(s *discordgo.Session, guildID string) (*discordgo.ApplicationCommand, error) {
opts := lo.Map(lo.Keys(h.config.Tokens.m), func(item service, index int) *discordgo.ApplicationCommandOption {
return &discordgo.ApplicationCommandOption{
Expand All @@ -25,18 +33,12 @@ func (h *TotpHandler) CreateTotpApplicationCommand(s *discordgo.Session, guildID
})
return s.ApplicationCommandCreate(s.State.User.ID, guildID, &discordgo.ApplicationCommand{
Name: "2fa",
Description: "get 2fa code",
Description: "2faコードを取得することができます",
Type: discordgo.ChatApplicationCommand,
Options: opts,
})
}

func NewTotpHandler(logger *zap.Logger, config Config) *TotpHandler {
return &TotpHandler{
logger: logger,
config: config,
}
}
func (h *TotpHandler) HandleIntractionCreate(s *discordgo.Session, i *discordgo.InteractionCreate) error {
h.logger.Debug("channel IDs", zap.Strings("channel IDs", h.config.AllowChannelIDs), zap.String("channel ID", i.ChannelID))
if !lo.Contains(h.config.AllowChannelIDs, i.ChannelID) {
Expand All @@ -56,10 +58,10 @@ func (h *TotpHandler) HandleIntractionCreate(s *discordgo.Session, i *discordgo.
h.logger.Warn("svc not found", zap.String("service name", i.ApplicationCommandData().Name))
return fmt.Errorf("not exist sub command")
}
totpClient = &totpGen{secret: string(tok)}
totpClient = &TotpGen{secret: string(tok)}
code, err := totpClient.GenerateCode(time.Now())
if err != nil {
h.logger.Error("generate code error", zap.Error(err))
h.logger.Error("generate code error", zap.Error(err), zap.String("serviceName", svcName))
return s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Expand All @@ -70,7 +72,7 @@ func (h *TotpHandler) HandleIntractionCreate(s *discordgo.Session, i *discordgo.
return s.InteractionRespond(i.Interaction, &discordgo.InteractionResponse{
Type: discordgo.InteractionResponseChannelMessageWithSource,
Data: &discordgo.InteractionResponseData{
Content: fmt.Sprintf("%sの2faコードは`%s`です\n", svcName, code),
Content: fmt.Sprintf("2faコードは`%s`です\n", code),
},
})
}
Expand All @@ -79,12 +81,16 @@ type TotpClient interface {
GenerateCode(t time.Time) (string, error)
}

type totpGen struct {
type TotpGen struct {
secret string
}

func (g *totpGen) GenerateCode(t time.Time) (string, error) {
return totp.GenerateCode(g.secret, t)
func (g *TotpGen) GenerateCode(t time.Time) (string, error) {

return totp.GenerateCode(trimInnerWhite(g.secret), t)
}
func trimInnerWhite(secret string) string {
return strings.Join(strings.Fields(secret), ``)
}

func IntractionCreateHandlerRouter(logger *zap.Logger, config Config, cmdName string) (func(s *discordgo.Session, i *discordgo.InteractionCreate) error, error) {
Expand Down
46 changes: 46 additions & 0 deletions discord_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package main

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
)

func TestTotpGenGenerateCode(t *testing.T) {
type testCase struct {
secret string
tokenCode string
wantErr bool
}
testCases := map[string]testCase{
"with no white": {
secret: "xxxxxxxxxxxxxxxxxxxx",
tokenCode: `000241`,
wantErr: false,
},
"with internal white": {
secret: "xxxxx xxxxx xxxxx xxxxx",
tokenCode: `000241`,
wantErr: false,
},
"with internal white and leading, trailing spaces": {
secret: " xxxxx xxxxx xxxxx xxxxx ",
tokenCode: `000241`,
wantErr: false,
},
}
for k, v := range testCases {
t.Run(k, func(t *testing.T) {
t.Parallel()
tokGen := TotpGen{secret: v.secret}
code, err := tokGen.GenerateCode(time.Time{})
if v.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
assert.Equal(t, v.tokenCode, code)
})
}
}
2 changes: 1 addition & 1 deletion dockerfiles/release/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
FROM gcr.io/distroless/static-debian11:nonroot
COPY discotp /
COPY --chown=nonroot:nonroot discotp /
ENTRYPOINT [ "/discotp" ]
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,21 @@ require (
github.com/caarlos0/env/v6 v6.10.1
github.com/pquerna/otp v1.4.0
github.com/samber/lo v1.37.0
github.com/stretchr/testify v1.8.1
go.uber.org/zap v1.24.0
)

require (
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gorilla/websocket v1.4.2 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/stretchr/testify v1.8.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/objx v0.5.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
golang.org/x/sys v0.0.0-20220908164124-27713097b956 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw=
github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
Expand Down Expand Up @@ -47,6 +48,7 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
16 changes: 11 additions & 5 deletions main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package main

import (
"fmt"
"log"
"os"
"os/signal"
Expand Down Expand Up @@ -31,14 +32,19 @@ func (t *TotpToks) UnmarshalText(text []byte) error {
k service
v totpTok
}
ress := lo.Map(strings.Split(string(text), `,`), func(item string, index int) kv {
ss := lo.Map(strings.SplitN(item, `:`, 2), func(item string, index int) string { return strings.TrimSpace(item) })
ress := lo.Map(strings.Split(string(text), `,`), func(item string, _ int) kv {
ss := lo.Map(strings.SplitN(item, `:`, 2), func(item string, _ int) string { return strings.TrimSpace(item) })
if len(ss) < 2 {
return kv{}
}
return kv{k: service(ss[0]), v: totpTok(ss[1])}
})
if lo.Contains(ress, kv{}) {
return fmt.Errorf("parse failed, text=%s", text)
}
t.m = lo.Associate(ress, func(item kv) (service, totpTok) { return item.k, item.v })
return nil
}
func (s service) String() string { return string(s) }

func main() {
config := &Config{}
Expand Down Expand Up @@ -89,6 +95,6 @@ func main() {
logger.Info("start listening")
stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt)
<-stop
logger.Info("stop signal handle")
s := <-stop
logger.Info("handle stop signal", zap.Stringer("signal", s))
}
73 changes: 73 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package main

import (
"encoding"
"testing"

"github.com/stretchr/testify/assert"
)

func TestTotpToksUnmarshalText(t *testing.T) {
type inOut struct {
in []byte
out TotpToks
expectErr bool
}
testCases := map[string]inOut{
"one input": {
in: []byte(`hoge:xxxxxxxxxxxx`),
out: TotpToks{map[service]totpTok{
"hoge": `xxxxxxxxxxxx`,
}},
expectErr: false,
},
"multi input": {
in: []byte(`hoge:xxxxx,fuga:111111`),
out: TotpToks{map[service]totpTok{
"hoge": "xxxxx",
"fuga": "111111",
}},
expectErr: false,
},
"no token with service name": {
in: []byte(`hoge`),
out: TotpToks{nil},
expectErr: true,
},
"no token in last service": {
in: []byte(`hoge:xxxxxxx,fuga`),
out: TotpToks{nil},
expectErr: true,
},
"empty input": {
in: []byte(``),
out: TotpToks{m: nil},
expectErr: true,
},
"multi input with inner spaces": {
in: []byte(`hoge:xxxxx xxxxx xxxxx, fuga: yyyyy yyyyy yyyyy`),
out: TotpToks{map[service]totpTok{
"hoge": "xxxxx xxxxx xxxxx",
"fuga": "yyyyy yyyyy yyyyy",
},
},
expectErr: false,
},
}
for k, v := range testCases {
t.Run(k, func(t *testing.T) {
var buf TotpToks
err := buf.UnmarshalText(v.in)
if v.expectErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
assert.Equal(t, v.out, buf)
})
}
}

func TestTotpToksImpl(t *testing.T) {
assert.Implements(t, (*encoding.TextUnmarshaler)(nil), new(TotpToks))
}

0 comments on commit d44b2c1

Please sign in to comment.