Skip to content

Commit

Permalink
Merge branch 'go-shiori:master' into mac-launch-agent
Browse files Browse the repository at this point in the history
  • Loading branch information
cdaein authored Nov 3, 2024
2 parents d7392ff + 6b6d5f3 commit 2de02c6
Show file tree
Hide file tree
Showing 18 changed files with 305 additions and 82 deletions.
23 changes: 18 additions & 5 deletions .github/workflows/_buildx.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
name: "Build Docker"

on: workflow_call
on:
workflow_call:
inputs:
tag_prefix:
description: 'The tag prefix to use'
required: false
type: string
default: ''
dockerfile:
description: 'The Dockerfile to use'
required: false
type: string
default: 'Dockerfile'

jobs:
buildx:
runs-on: ubuntu-latest
Expand All @@ -22,7 +35,7 @@ jobs:
run: |
REPO=ghcr.io/${{ github.repository }}
TAG=$(git describe --tags)
echo "tag_flags=--tag $REPO:$TAG" >> $GITHUB_ENV
echo "tag_flags=--tag $REPO:${{ inputs.tag_prefix }}$TAG" >> $GITHUB_ENV
# New tagged version
- name: Prepare version push tags
Expand All @@ -36,18 +49,18 @@ jobs:
else
TAG2="latest"
fi
echo "tag_flags=--tag $REPO:$TAG --tag $REPO:$TAG2" >> $GITHUB_ENV
echo "tag_flags=--tag $REPO:${{ inputs.tag_prefix }}$TAG --tag $REPO:${{ inputs.tag_prefix }}$TAG2" >> $GITHUB_ENV
# Every pull request
- name: Prepare pull request tags
if: github.event_name == 'pull_request'
run: |
echo "tag_flags=--tag ${{ github.ref }}" >> $GITHUB_ENV
REPO=ghcr.io/${{ github.repository }}
echo "tag_flags=--tag $REPO:pr-${{ github.event.pull_request.number }}" >> $GITHUB_ENV
echo "tag_flags=--tag $REPO:${{ inputs.tag_prefix }}pr-${{ github.event.pull_request.number }}" >> $GITHUB_ENV
- name: Buildx
run: |
set -x
echo "${{ secrets.GITHUB_TOKEN }}" | docker login -u "${{ github.repository_owner }}" --password-stdin ghcr.io
make buildx CONTAINER_BUILDX_OPTIONS="--push ${{ env.tag_flags }}"
make buildx CONTAINERFILE_NAME=${{ inputs.dockerfile }} CONTAINER_BUILDX_OPTIONS="--push ${{ env.tag_flags }}"
8 changes: 8 additions & 0 deletions .github/workflows/pull_request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,11 @@ jobs:
# only build on pull requests from the same repo for now
if: github.event.pull_request.head.repo.full_name == github.repository
uses: ./.github/workflows/_buildx.yml
call-buildx-alpine:
needs: call-gorelease
# only build on pull requests from the same repo for now
if: github.event.pull_request.head.repo.full_name == github.repository
uses: ./.github/workflows/_buildx.yml
with:
tag_prefix: alpine-
dockerfile: Dockerfile.alpine
8 changes: 8 additions & 0 deletions .github/workflows/push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,11 @@ jobs:
call-buildx:
needs: call-gorelease
uses: ./.github/workflows/_buildx.yml
call-buildx-alpine:
needs: call-gorelease
# only build on pull requests from the same repo for now
if: github.event.pull_request.head.repo.full_name == github.repository
uses: ./.github/workflows/_buildx.yml
with:
tag_prefix: alpine-
dockerfile: Dockerfile.alpine
8 changes: 8 additions & 0 deletions .github/workflows/version_bump.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,11 @@ jobs:
call-buildx:
needs: call-gorelease
uses: ./.github/workflows/_buildx.yml
call-buildx-alpine:
needs: call-gorelease
# only build on pull requests from the same repo for now
if: github.event.pull_request.head.repo.full_name == github.repository
uses: ./.github/workflows/_buildx.yml
with:
tag_prefix: alpine-
dockerfile: Dockerfile.alpine
5 changes: 2 additions & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# Build stage
ARG ALPINE_VERSION
ARG GOLANG_VERSION
ARG ALPINE_VERSION=3.19

FROM docker.io/library/alpine:${ALPINE_VERSION} AS builder
ARG TARGETARCH
Expand All @@ -14,7 +13,7 @@ RUN apk add --no-cache ca-certificates tzdata && \
# Server image
FROM scratch

ENV PORT 8080
ENV PORT=8080
ENV SHIORI_DIR=/shiori
WORKDIR ${SHIORI_DIR}

Expand Down
23 changes: 23 additions & 0 deletions Dockerfile.alpine
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
ARG ALPINE_VERSION=3.19

FROM docker.io/library/alpine:${ALPINE_VERSION}
ARG TARGETARCH
ARG TARGETOS
ARG TARGETVARIANT
COPY dist/shiori_${TARGETOS}_${TARGETARCH}${TARGETVARIANT}/shiori /usr/bin/shiori
RUN apk add --no-cache ca-certificates tzdata && \
chmod +x /usr/bin/shiori && \
rm -rf /tmp/* && \
apk cache clean

ENV PORT=8080
ENV SHIORI_DIR=/shiori
WORKDIR ${SHIORI_DIR}

LABEL org.opencontainers.image.source="https://github.com/go-shiori/shiori"
LABEL maintainer="Felipe Martin <[email protected]>"

EXPOSE ${PORT}

ENTRYPOINT ["/usr/bin/shiori"]
CMD ["server"]
4 changes: 3 additions & 1 deletion docs/Usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,10 @@ The above command will :

After you've run the container in background, you can access console of the container:

> In order to be able to access the container and execute commands you need to use the `alpine-` prefixed images.
```
docker exec -it shiori sh
docker exec -it shiori ash
```

Now you can use `shiori` like normal. If you've finished, you can stop and remove the container by running :
Expand Down
37 changes: 20 additions & 17 deletions internal/domains/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,36 +16,35 @@ type AccountsDomain struct {
deps *dependencies.Dependencies
}

type JWTClaim struct {
jwt.RegisteredClaims

Account *model.Account
}

func (d *AccountsDomain) CheckToken(ctx context.Context, userJWT string) (*model.Account, error) {
token, err := jwt.ParseWithClaims(userJWT, &JWTClaim{}, func(token *jwt.Token) (interface{}, error) {
func (d *AccountsDomain) ParseToken(userJWT string) (*model.JWTClaim, error) {
token, err := jwt.ParseWithClaims(userJWT, &model.JWTClaim{}, func(token *jwt.Token) (interface{}, error) {
// Validate algorithm
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}

return d.deps.Config.Http.SecretKey, nil
})
if err != nil {
return nil, errors.Wrap(err, "error parsing token")
}

if claims, ok := token.Claims.(*JWTClaim); ok && token.Valid {
if claims.Account.ID > 0 {
return claims.Account, nil
}
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*model.JWTClaim); ok && token.Valid {
return claims, nil
}

return nil, fmt.Errorf("error obtaining user from JWT claims")
}

func (d *AccountsDomain) CheckToken(ctx context.Context, userJWT string) (*model.Account, error) {
claims, err := d.ParseToken(userJWT)
if err != nil {
return nil, fmt.Errorf("error parsing token: %w", err)
}

if claims.Account.ID > 0 {
return claims.Account, nil
}
return nil, fmt.Errorf("error obtaining user from JWT claims")
return nil, fmt.Errorf("error obtaining user from JWT claims: %w", err)
}

func (d *AccountsDomain) GetAccountFromCredentials(ctx context.Context, username, password string) (*model.Account, error) {
Expand All @@ -62,6 +61,10 @@ func (d *AccountsDomain) GetAccountFromCredentials(ctx context.Context, username
}

func (d *AccountsDomain) CreateTokenForAccount(account *model.Account, expiration time.Time) (string, error) {
if account == nil {
return "", fmt.Errorf("account is nil")
}

claims := jwt.MapClaims{
"account": account.ToDTO(),
"exp": expiration.UTC().Unix(),
Expand Down
145 changes: 145 additions & 0 deletions internal/domains/accounts_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package domains_test

import (
"context"
"testing"
"time"

"github.com/go-shiori/shiori/internal/domains"
"github.com/go-shiori/shiori/internal/model"
"github.com/go-shiori/shiori/internal/testutil"
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/require"
)

func TestAccountsDomainParseToken(t *testing.T) {
ctx := context.TODO()
logger := logrus.New()
_, deps := testutil.GetTestConfigurationAndDependencies(t, ctx, logger)
domain := domains.NewAccountsDomain(deps)

t.Run("valid token", func(t *testing.T) {
// Create a valid token
token, err := domain.CreateTokenForAccount(
testutil.GetValidAccount(),
time.Now().Add(time.Hour*1),
)
require.NoError(t, err)

claims, err := domain.ParseToken(token)
require.NoError(t, err)
require.NotNil(t, claims)
require.Equal(t, 99, claims.Account.ID)
})

t.Run("invalid token", func(t *testing.T) {
claims, err := domain.ParseToken("invalid-token")
require.Error(t, err)
require.Nil(t, claims)
})
}

func TestAccountsDomainCheckToken(t *testing.T) {
ctx := context.TODO()
logger := logrus.New()
_, deps := testutil.GetTestConfigurationAndDependencies(t, ctx, logger)
domain := domains.NewAccountsDomain(deps)

t.Run("valid token", func(t *testing.T) {
// Create a valid token
token, err := domain.CreateTokenForAccount(
testutil.GetValidAccount(),
time.Now().Add(time.Hour*1),
)
require.NoError(t, err)

acc, err := domain.CheckToken(ctx, token)
require.NoError(t, err)
require.NotNil(t, acc)
require.Equal(t, 99, acc.ID)
})

t.Run("expired token", func(t *testing.T) {
// Create an expired token
token, err := domain.CreateTokenForAccount(
testutil.GetValidAccount(),
time.Now().Add(time.Hour*-1),
)
require.NoError(t, err)

acc, err := domain.CheckToken(ctx, token)
require.Error(t, err)
require.Nil(t, acc)
})
}

func TestAccountsDomainGetAccountFromCredentials(t *testing.T) {
ctx := context.TODO()
logger := logrus.New()
_, deps := testutil.GetTestConfigurationAndDependencies(t, ctx, logger)
domain := domains.NewAccountsDomain(deps)

require.NoError(t, deps.Database.SaveAccount(ctx, model.Account{
Username: "test",
Password: "test",
}))

t.Run("valid credentials", func(t *testing.T) {
acc, err := domain.GetAccountFromCredentials(ctx, "test", "test")
require.NoError(t, err)
require.NotNil(t, acc)
require.Equal(t, "test", acc.Username)
})

t.Run("invalid credentials", func(t *testing.T) {
acc, err := domain.GetAccountFromCredentials(ctx, "test", "invalid")
require.Error(t, err)
require.Nil(t, acc)
})

t.Run("invalid username", func(t *testing.T) {
acc, err := domain.GetAccountFromCredentials(ctx, "nope", "invalid")
require.Error(t, err)
require.Nil(t, acc)
})

}

func TestAccountsDomainCreateTokenForAccount(t *testing.T) {
ctx := context.TODO()
logger := logrus.New()
_, deps := testutil.GetTestConfigurationAndDependencies(t, ctx, logger)
domain := domains.NewAccountsDomain(deps)

t.Run("valid account", func(t *testing.T) {
token, err := domain.CreateTokenForAccount(
testutil.GetValidAccount(),
time.Now().Add(time.Hour*1),
)
require.NoError(t, err)
require.NotEmpty(t, token)
})

t.Run("nil account", func(t *testing.T) {
token, err := domain.CreateTokenForAccount(
nil,
time.Now().Add(time.Hour*1),
)
require.Error(t, err)
require.Empty(t, token)
})

t.Run("token expiration is valid", func(t *testing.T) {
expiration := time.Now().Add(time.Hour * 9)
token, err := domain.CreateTokenForAccount(
testutil.GetValidAccount(),
expiration,
)
require.NoError(t, err)
require.NotEmpty(t, token)
claims, err := domain.ParseToken(token)
require.NoError(t, err)
require.NotNil(t, claims)
require.Equal(t, expiration.Unix(), claims.ExpiresAt.Time.Unix())
})
}
1 change: 1 addition & 0 deletions internal/http/middleware/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ func AuthMiddleware(deps *dependencies.Dependencies) gin.HandlerFunc {

account, err := deps.Domains.Auth.CheckToken(c, token)
if err != nil {
deps.Log.WithError(err).Error("Failed to check token")
return
}

Expand Down
8 changes: 4 additions & 4 deletions internal/http/middleware/auth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@ func TestAuthMiddleware(t *testing.T) {
})

t.Run("test authorization header", func(t *testing.T) {
account := model.Account{Username: "shiori"}
token, err := deps.Domains.Auth.CreateTokenForAccount(&account, time.Now().Add(time.Minute))
account := testutil.GetValidAccount()
token, err := deps.Domains.Auth.CreateTokenForAccount(account, time.Now().Add(time.Minute))
require.NoError(t, err)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
Expand All @@ -74,8 +74,8 @@ func TestAuthMiddleware(t *testing.T) {
})

t.Run("test authorization cookie", func(t *testing.T) {
account := model.Account{Username: "shiori"}
token, err := deps.Domains.Auth.CreateTokenForAccount(&account, time.Now().Add(time.Minute))
account := testutil.GetValidAccount()
token, err := deps.Domains.Auth.CreateTokenForAccount(account, time.Now().Add(time.Minute))
require.NoError(t, err)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
Expand Down
Loading

0 comments on commit 2de02c6

Please sign in to comment.