├── .travis.yml
├── LICENSE
├── README.md
├── actions.go
├── ap
├── actor.go
├── actor_map.go
├── apdb.go
├── c2s.go
├── clock.go
├── common.go
├── database.go
├── instance_actor_common.go
├── instance_actor_s2s.go
├── s2s.go
└── util.go
├── app
├── application.go
├── database.go
├── framework.go
├── funcs.go
├── paths.go
├── router.go
└── software.go
├── cmdline.go
├── dep_inj.go
├── example
├── README.md
├── app.go
├── main.go
├── services.go
└── templates
│ ├── auth.tmpl
│ ├── bad_request.tmpl
│ ├── create_note.tmpl
│ ├── followers.tmpl
│ ├── followers_request.tmpl
│ ├── following.tmpl
│ ├── following_create.tmpl
│ ├── footer.tmpl
│ ├── header.tmpl
│ ├── home.tmpl
│ ├── inbox.tmpl
│ ├── inline_css.tmpl
│ ├── internal_error.tmpl
│ ├── list_notes.tmpl
│ ├── list_users.tmpl
│ ├── login.tmpl
│ ├── nav.tmpl
│ ├── not_allowed.tmpl
│ ├── not_found.tmpl
│ ├── note.tmpl
│ ├── outbox.tmpl
│ └── user.tmpl
├── framework
├── clarke.go
├── client.go
├── config.go
├── config
│ ├── config.go
│ ├── core_config.go
│ └── verify.go
├── conn
│ ├── host_limiter.go
│ ├── retrier.go
│ └── transport.go
├── db
│ ├── db.go
│ └── postgres.go
├── framework.go
├── handler.go
├── mux_wrapper.go
├── nodeinfo
│ ├── nodeinfo.go
│ ├── nodeinfo2.go
│ └── pkg.go
├── oauth2
│ ├── oauth.go
│ └── proxy.go
├── prompt.go
├── prompt_config.go
├── router.go
├── server.go
├── web
│ ├── sessions.go
│ └── user_agent.go
└── webfinger
│ └── webfinger.go
├── go.mod
├── go.sum
├── models
├── client_infos.go
├── credentials.go
├── delivery_attempts.go
├── fed_data.go
├── followers.go
├── following.go
├── inboxes.go
├── liked.go
├── local_data.go
├── model.go
├── outboxes.go
├── policies.go
├── private_keys.go
├── resolutions.go
├── serialization.go
├── sql_dialect.go
├── test
│ ├── main.go
│ └── testdata.go
├── token_infos.go
└── users.go
├── paths
├── iri.go
└── query.go
├── pkg.go
├── run.go
├── services
├── activitystreams.go
├── any.go
├── crypto.go
├── data.go
├── delivery_attempts.go
├── followers.go
├── following.go
├── inboxes.go
├── liked.go
├── nodeinfo.go
├── oauth2.go
├── outboxes.go
├── pagination.go
├── policies.go
├── private_keys.go
├── tx.go
└── users.go
└── util
├── context.go
├── log.go
├── resolvers.go
└── safe_start_stop.go
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 | go:
3 | - 1.15
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # apcore
2 |
3 | > Server framework for quickly building ActivityPub applications
4 |
5 | *Under Construction*
6 |
7 | [![Build Status][Build-Status-Image]][Build-Status-Url] [![Go Reference][Go-Reference-Image]][Go-Reference-Url]
8 | [![Go Report Card][Go-Report-Card-Image]][Go-Report-Card-Url] [![License][License-Image]][License-Url]
9 | [![Chat][Chat-Image]][Chat-Url] [![OpenCollective][OpenCollective-Image]][OpenCollective-Url]
10 |
11 | `go get github.com/go-fed/apcore`
12 |
13 | apcore is a powerful single server
14 | [ActivityPub](https://www.w3.org/TR/activitypub)
15 | framework for performant Fediverse applications.
16 |
17 | It is built on top of the
18 | [go-fed/activity](https://github.com/go-fed/activity)
19 | suite of libraries, which means it can readily allow application developers to
20 | iterate and leverage new
21 | [ActivityStreams](https://www.w3.org/TR/activitystreams-core)
22 | or RDF vocabularies.
23 |
24 | ## Features
25 |
26 | *This list is a work in progress.*
27 |
28 | * Uses `go-fed/activity`
29 | * ActivityPub S2S (Server-to-Server) Protocol supported
30 | * ActivityPub C2S (Client-to-Server) Protocol supported
31 | * Both S2S and C2S can be used at the same time
32 | * Comes with the Core & Extended ActivityStreams types
33 | * Readily expands to support new ActivityStreams types and/or RDF vocabularies
34 | * Federation & Moderation Policy System
35 | * Administrators and/or users can create policies to customize their federation experience
36 | * Auditable results of applying policies on incoming federated data
37 | * Supports common out-of-the-box command-line commands for:
38 | * Initializing a database with the appropriate `apcore` tables as well as your application-specific tables
39 | * Initializing a new administrator account
40 | * Creating a server configuration file in a guided flow
41 | * Comprehensive help command
42 | * Guided command line flow for administrators for all the above tasks, featuring Clarke the Cow
43 | * Configuration file support
44 | * Add your configuration options to the existing `apcore` configuration options
45 | * Administrators can customize their ActivityPub and your app's experience
46 | * Database support
47 | * Currently, only PostgreSQL supported
48 | * Others can be added with a some SQL work, in the future
49 | * No ORM overhead
50 | * Your custom application has access to `apcore` tables, and more
51 | * OAuth2 support
52 | * Easy API to build authorization grant and validation flows
53 | * Handles server side state for you
54 | * Webfinger & Host-Meta support
55 |
56 | ## How To Use This Framework
57 |
58 | *This guide is a work in progress.*
59 |
60 | Building an application is not an easy thing to do, but following these steps
61 | reduces the cost of building a *federated* application:
62 |
63 | 0. Implement the `apcore.Application` interface.
64 | 0. Call `apcore.Run` with your implementation in `main`.
65 |
66 | The most work is in the first step, as your application logic is able to live as
67 | functional closures as the `Application` is used within the `apcore` framework.
68 | See the documentation on the `Application` interface for specific details.
69 |
70 | [Build-Status-Image]: https://travis-ci.org/go-fed/apcore.svg?branch=master
71 | [Build-Status-Url]: https://travis-ci.org/go-fed/apcore
72 | [Go-Reference-Image]: https://pkg.go.dev/badge/github.com/go-fed/apcore.svg
73 | [Go-Reference-Url]: https://pkg.go.dev/github.com/go-fed/apcore
74 | [Go-Report-Card-Image]: https://goreportcard.com/badge/github.com/go-fed/apcore
75 | [Go-Report-Card-Url]: https://goreportcard.com/report/github.com/go-fed/apcore
76 | [License-Image]: https://img.shields.io/github/license/go-fed/apcore?color=blue
77 | [License-Url]: https://www.gnu.org/licenses/agpl-3.0.en.html
78 | [Chat-Image]: https://img.shields.io/matrix/go-fed:feneas.org?server_fqdn=matrix.org
79 | [Chat-Url]: https://matrix.to/#/!BLOSvIyKTDLIVjRKSc:feneas.org?via=feneas.org&via=matrix.org
80 | [OpenCollective-Image]: https://img.shields.io/opencollective/backers/go-fed-activitypub-labs
81 | [OpenCollective-Url]: https://opencollective.com/go-fed-activitypub-labs
82 |
--------------------------------------------------------------------------------
/actions.go:
--------------------------------------------------------------------------------
1 | // apcore is a server framework for implementing an ActivityPub application.
2 | // Copyright (C) 2020 Cory Slep
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package apcore
18 |
19 | import (
20 | "context"
21 |
22 | "github.com/go-fed/apcore/app"
23 | "github.com/go-fed/apcore/framework"
24 | "github.com/go-fed/apcore/services"
25 | "github.com/go-fed/apcore/util"
26 | )
27 |
28 | func doCreateTables(configFilePath string, a app.Application, debug bool, scheme string) error {
29 | db, d, ms, cfg, err := newModels(configFilePath, a, debug, scheme)
30 | if err != nil {
31 | return err
32 | }
33 | defer db.Close()
34 | tx, err := db.BeginTx(context.Background(), nil)
35 | if err != nil {
36 | return err
37 | }
38 | defer tx.Rollback()
39 | for _, m := range ms {
40 | if err := m.CreateTable(tx, d); err != nil {
41 | return err
42 | }
43 | }
44 | if err = tx.Commit(); err != nil {
45 | return err
46 | }
47 | return a.CreateTables(context.Background(), &services.Any{db}, cfg, debug)
48 | }
49 |
50 | func doInitAdmin(configFilePath string, a app.Application, debug bool, scheme string) error {
51 | db, users, c, err := newUserService(configFilePath, a, debug, scheme)
52 | if err != nil {
53 | return err
54 | }
55 |
56 | // Prompt for admin information
57 | p := services.CreateUserParameters{
58 | Scheme: scheme,
59 | Host: c.ServerConfig.Host,
60 | RSAKeySize: c.ServerConfig.RSAKeySize,
61 | HashParams: services.HashPasswordParameters{
62 | SaltSize: c.ServerConfig.SaltSize,
63 | BCryptStrength: c.ServerConfig.BCryptStrength,
64 | },
65 | }
66 | var password string
67 | p.Username, p.Email, password, err = framework.PromptAdminUser()
68 |
69 | // Create the user in the database
70 | defer db.Close()
71 | tx, err := db.BeginTx(context.Background(), nil)
72 | if err != nil {
73 | return err
74 | }
75 | defer tx.Rollback()
76 | userID, err := users.CreateAdminUser(util.Context{context.Background()}, p, password)
77 | if err != nil {
78 | return err
79 | }
80 | if err = tx.Commit(); err != nil {
81 | return err
82 | }
83 | return a.OnCreateAdminUser(context.Background(), userID, &services.Any{db}, c)
84 | }
85 |
86 | func doInitData(configFilePath string, a app.Application, debug bool, scheme string) error {
87 | db, users, c, err := newUserService(configFilePath, a, debug, scheme)
88 | if err != nil {
89 | return err
90 | }
91 |
92 | // Create the server actor in the database
93 | defer db.Close()
94 | tx, err := db.BeginTx(context.Background(), nil)
95 | if err != nil {
96 | return err
97 | }
98 | defer tx.Rollback()
99 | _, err = users.CreateInstanceActorSingleton(util.Context{context.Background()}, scheme, c.ServerConfig.Host, c.ServerConfig.RSAKeySize)
100 | if err != nil {
101 | return err
102 | }
103 | return tx.Commit()
104 | }
105 |
106 | func doInitServerProfile(configFilePath string, a app.Application, debug bool, scheme string) error {
107 | db, users, c, err := newUserService(configFilePath, a, debug, scheme)
108 | if err != nil {
109 | return err
110 | }
111 | defer db.Close()
112 |
113 | sp, err := framework.PromptServerProfile(scheme, c.ServerConfig.Host)
114 | if err != nil {
115 | return err
116 | }
117 | tx, err := db.BeginTx(context.Background(), nil)
118 | if err != nil {
119 | return err
120 | }
121 | defer tx.Rollback()
122 | err = users.SetServerPreferences(util.Context{context.Background()}, sp)
123 | if err != nil {
124 | return err
125 | }
126 | return tx.Commit()
127 | }
128 |
--------------------------------------------------------------------------------
/ap/actor.go:
--------------------------------------------------------------------------------
1 | // apcore is a server framework for implementing an ActivityPub application.
2 | // Copyright (C) 2019 Cory Slep
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package ap
18 |
19 | import (
20 | "fmt"
21 |
22 | "github.com/go-fed/activity/pub"
23 | "github.com/go-fed/apcore/app"
24 | "github.com/go-fed/apcore/framework/config"
25 | "github.com/go-fed/apcore/framework/conn"
26 | "github.com/go-fed/apcore/framework/oauth2"
27 | "github.com/go-fed/apcore/services"
28 | )
29 |
30 | func NewActor(c *config.Config,
31 | a app.Application,
32 | clock *Clock,
33 | db *Database,
34 | apdb *APDB,
35 | o *oauth2.Server,
36 | pk *services.PrivateKeys,
37 | po *services.Policies,
38 | f *services.Followers,
39 | u *services.Users,
40 | tc *conn.Controller) (actor pub.Actor, err error) {
41 |
42 | common := NewCommonBehavior(a, db, tc, o, pk)
43 | ca, isC2S := a.(app.C2SApplication)
44 | sa, isS2S := a.(app.S2SApplication)
45 | if !isC2S && !isS2S {
46 | err = fmt.Errorf("the Application is neither a C2SApplication nor a S2SApplication")
47 | } else if isC2S && isS2S {
48 | c2s := NewSocialBehavior(ca, o)
49 | s2s := NewFederatingBehavior(c, sa, db, po, pk, f, u, tc)
50 | actor = pub.NewActor(
51 | common,
52 | c2s,
53 | s2s,
54 | apdb,
55 | clock)
56 | } else if isC2S {
57 | c2s := NewSocialBehavior(ca, o)
58 | actor = pub.NewSocialActor(
59 | common,
60 | c2s,
61 | apdb,
62 | clock)
63 | } else {
64 | s2s := NewFederatingBehavior(c, sa, db, po, pk, f, u, tc)
65 | actor = pub.NewFederatingActor(
66 | common,
67 | s2s,
68 | apdb,
69 | clock)
70 | }
71 | return
72 | }
73 |
--------------------------------------------------------------------------------
/ap/actor_map.go:
--------------------------------------------------------------------------------
1 | // apcore is a server framework for implementing an ActivityPub application.
2 | // Copyright (C) 2019 Cory Slep
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package ap
18 |
19 | import (
20 | "github.com/go-fed/activity/pub"
21 | "github.com/go-fed/apcore/framework/config"
22 | "github.com/go-fed/apcore/framework/conn"
23 | "github.com/go-fed/apcore/paths"
24 | "github.com/go-fed/apcore/services"
25 | )
26 |
27 | func NewActorMap(c *config.Config,
28 | clock *Clock,
29 | db *Database,
30 | apdb *APDB,
31 | pk *services.PrivateKeys,
32 | f *services.Followers,
33 | tc *conn.Controller) (actorMap map[paths.Actor]pub.Actor) {
34 | actorMap = make(map[paths.Actor]pub.Actor, 1)
35 | actorMap[paths.InstanceActor] = newInstanceActor(c, clock, db, apdb, pk, f, tc)
36 | return
37 | }
38 |
39 | func newInstanceActor(c *config.Config,
40 | clock *Clock,
41 | db *Database,
42 | apdb *APDB,
43 | pk *services.PrivateKeys,
44 | f *services.Followers,
45 | tc *conn.Controller) (actor pub.Actor) {
46 | common := newInstanceActorCommonBehavior(db, tc, pk)
47 | s2s := newInstanceActorFederatingBehavior(c, db, pk, f, tc)
48 | actor = pub.NewFederatingActor(common, s2s, apdb, clock)
49 | return
50 | }
51 |
--------------------------------------------------------------------------------
/ap/apdb.go:
--------------------------------------------------------------------------------
1 | // apcore is a server framework for implementing an ActivityPub application.
2 | // Copyright (C) 2019 Cory Slep
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package ap
18 |
19 | import (
20 | "context"
21 | "fmt"
22 | "net/url"
23 | "sync"
24 |
25 | "github.com/go-fed/activity/pub"
26 | "github.com/go-fed/activity/streams/vocab"
27 | "github.com/go-fed/apcore/app"
28 | )
29 |
30 | var _ pub.Database = &APDB{}
31 |
32 | type APDB struct {
33 | *Database
34 | // Use sync.Map, which is specially optimized:
35 | //
36 | // "The Map type is optimized [...] when the entry for a given key is
37 | // only ever written once but read many times, as in caches that only
38 | // grow"
39 | //
40 | // This means we only ever append to the map during the lifetime of the
41 | // running application. This may become a scaling bottleneck in the
42 | // future, but unsure how the performance will look in practice.
43 | //
44 | // This map will only store *sync.Mutex, each is 4 bytes. Assuming that
45 | // conservatively the average key is a string of 124 bytes, this means
46 | // each entry is 128 bytes of memory.
47 | //
48 | // If this map holds 2,000,000 entries then it would take 256 MB of
49 | // memory. To take up 1 GB, 7,812,500 entries are needed. If one entry
50 | // is added per second, then in 90 days it will take up 1 GB of memory.
51 | //
52 | // TODO: Address this unbounded growth for memory-constrained or very
53 | // long running applications.
54 | locks *sync.Map
55 | app app.Application
56 | }
57 |
58 | func NewAPDB(db *Database, a app.Application) *APDB {
59 | return &APDB{
60 | Database: db,
61 | locks: &sync.Map{},
62 | app: a,
63 | }
64 | }
65 |
66 | func (a *APDB) Lock(c context.Context, id *url.URL) error {
67 | mui, _ := a.locks.LoadOrStore(id.String(), &sync.Mutex{})
68 | if mu, ok := mui.(*sync.Mutex); !ok {
69 | return fmt.Errorf("lock for Lock is not a *sync.Mutex")
70 | } else {
71 | mu.Lock()
72 | return nil
73 | }
74 | }
75 |
76 | func (a *APDB) Unlock(c context.Context, id *url.URL) error {
77 | mui, _ := a.locks.Load(id.String())
78 | if mu, ok := mui.(*sync.Mutex); !ok {
79 | return fmt.Errorf("lock for Unlock is not a *sync.Mutex")
80 | } else {
81 | mu.Unlock()
82 | return nil
83 | }
84 | }
85 |
86 | func (a *APDB) NewID(c context.Context, t vocab.Type) (id *url.URL, err error) {
87 | var path string
88 | path, err = a.app.NewIDPath(c, t)
89 | if err != nil {
90 | return
91 | }
92 | id = &url.URL{
93 | Scheme: a.scheme,
94 | Host: a.host,
95 | Path: path,
96 | }
97 | return
98 | }
99 |
--------------------------------------------------------------------------------
/ap/c2s.go:
--------------------------------------------------------------------------------
1 | // apcore is a server framework for implementing an ActivityPub application.
2 | // Copyright (C) 2019 Cory Slep
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package ap
18 |
19 | import (
20 | "context"
21 | "fmt"
22 | "net/http"
23 |
24 | "github.com/go-fed/activity/pub"
25 | "github.com/go-fed/activity/streams/vocab"
26 | "github.com/go-fed/apcore/app"
27 | "github.com/go-fed/apcore/framework/oauth2"
28 | "github.com/go-fed/apcore/util"
29 | oa2 "github.com/go-fed/oauth2"
30 | )
31 |
32 | var _ pub.SocialProtocol = &SocialBehavior{}
33 |
34 | type SocialBehavior struct {
35 | app app.C2SApplication
36 | o *oauth2.Server
37 | }
38 |
39 | func NewSocialBehavior(app app.C2SApplication, o *oauth2.Server) *SocialBehavior {
40 | return &SocialBehavior{
41 | app: app,
42 | o: o,
43 | }
44 | }
45 |
46 | func (s *SocialBehavior) PostOutboxRequestBodyHook(c context.Context, r *http.Request, data vocab.Type) (out context.Context, err error) {
47 | ctx := util.Context{c}
48 | ctx.WithActivityStream(data)
49 | out = ctx.Context
50 | return
51 | }
52 |
53 | func (s *SocialBehavior) AuthenticatePostOutbox(c context.Context, w http.ResponseWriter, r *http.Request) (out context.Context, authenticated bool, err error) {
54 | out = c
55 | var t oa2.TokenInfo
56 | t, authenticated, err = s.o.ValidateOAuth2AccessToken(w, r)
57 | if err != nil || !authenticated {
58 | return
59 | }
60 | // Authenticated, but must determine if permitted by the granted scope.
61 | authenticated, err = s.app.ScopePermitsPostOutbox(t.GetScope())
62 | return
63 | }
64 |
65 | func (s *SocialBehavior) SocialCallbacks(c context.Context) (wrapped pub.SocialWrappedCallbacks, other []interface{}, err error) {
66 | wrapped = pub.SocialWrappedCallbacks{}
67 | other = s.app.ApplySocialCallbacks(&wrapped)
68 | return
69 | }
70 |
71 | func (s *SocialBehavior) DefaultCallback(c context.Context, activity pub.Activity) error {
72 | return fmt.Errorf("Unhandled client Activity of type: %s", activity.GetTypeName())
73 | }
74 |
--------------------------------------------------------------------------------
/ap/clock.go:
--------------------------------------------------------------------------------
1 | // apcore is a server framework for implementing an ActivityPub application.
2 | // Copyright (C) 2019 Cory Slep
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package ap
18 |
19 | import (
20 | "time"
21 |
22 | "github.com/go-fed/activity/pub"
23 | )
24 |
25 | var _ pub.Clock = &Clock{}
26 |
27 | type Clock struct {
28 | loc *time.Location
29 | }
30 |
31 | // Creates new clock with IANA Time Zone database string
32 | func NewClock(location string) (c *Clock, err error) {
33 | c = &Clock{}
34 | c.loc, err = time.LoadLocation(location)
35 | return
36 | }
37 |
38 | func (c *Clock) Now() time.Time {
39 | return time.Now().In(c.loc)
40 | }
41 |
--------------------------------------------------------------------------------
/ap/common.go:
--------------------------------------------------------------------------------
1 | // apcore is a server framework for implementing an ActivityPub application.
2 | // Copyright (C) 2019 Cory Slep
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package ap
18 |
19 | import (
20 | "context"
21 | "crypto/rsa"
22 | "net/http"
23 | "net/url"
24 |
25 | "github.com/go-fed/activity/pub"
26 | "github.com/go-fed/activity/streams/vocab"
27 | "github.com/go-fed/apcore/app"
28 | "github.com/go-fed/apcore/framework/conn"
29 | "github.com/go-fed/apcore/framework/oauth2"
30 | "github.com/go-fed/apcore/paths"
31 | "github.com/go-fed/apcore/services"
32 | "github.com/go-fed/apcore/util"
33 | oa2 "github.com/go-fed/oauth2"
34 | )
35 |
36 | var _ pub.CommonBehavior = &CommonBehavior{}
37 |
38 | type CommonBehavior struct {
39 | app app.Application
40 | tc *conn.Controller
41 | o *oauth2.Server
42 | db *Database
43 | pk *services.PrivateKeys
44 | }
45 |
46 | func NewCommonBehavior(
47 | app app.Application,
48 | db *Database,
49 | tc *conn.Controller,
50 | o *oauth2.Server,
51 | pk *services.PrivateKeys) *CommonBehavior {
52 | return &CommonBehavior{
53 | app: app,
54 | tc: tc,
55 | o: o,
56 | db: db,
57 | pk: pk,
58 | }
59 | }
60 |
61 | func (a *CommonBehavior) AuthenticateGetInbox(c context.Context, w http.ResponseWriter, r *http.Request) (newCtx context.Context, authenticated bool, err error) {
62 | return a.authenticateGetRequest(util.Context{c}, w, r)
63 | }
64 |
65 | func (a *CommonBehavior) AuthenticateGetOutbox(c context.Context, w http.ResponseWriter, r *http.Request) (newCtx context.Context, authenticated bool, err error) {
66 | return a.authenticateGetRequest(util.Context{c}, w, r)
67 | }
68 |
69 | func (a *CommonBehavior) GetOutbox(c context.Context, r *http.Request) (ocp vocab.ActivityStreamsOrderedCollectionPage, err error) {
70 | ctx := util.Context{c}
71 | // IfChange
72 | var outboxIRI *url.URL
73 | if outboxIRI, err = ctx.CompleteRequestURL(); err != nil {
74 | return
75 | }
76 | if ctx.HasPrivateScope() {
77 | ocp, err = a.db.GetOutbox(c, outboxIRI)
78 | } else {
79 | ocp, err = a.db.GetPublicOutbox(c, outboxIRI)
80 | }
81 | // ThenChange(router.go)
82 | return
83 | }
84 |
85 | func (a *CommonBehavior) NewTransport(c context.Context, actorBoxIRI *url.URL, gofedAgent string) (t pub.Transport, err error) {
86 | ctx := util.Context{c}
87 | var userUUID paths.UUID
88 | userUUID, err = ctx.UserPathUUID()
89 | if err != nil {
90 | return
91 | }
92 | var privKey *rsa.PrivateKey
93 | var pubKeyURL *url.URL
94 | privKey, pubKeyURL, err = a.pk.GetUserHTTPSignatureKey(util.Context{c}, userUUID)
95 | if err != nil {
96 | return
97 | }
98 | return a.tc.Get(privKey, pubKeyURL.String())
99 | }
100 |
101 | func (a *CommonBehavior) authenticateGetRequest(c util.Context, w http.ResponseWriter, r *http.Request) (newCtx context.Context, authenticated bool, err error) {
102 | newCtx = c
103 | var t oa2.TokenInfo
104 | var oAuthAuthenticated bool
105 | t, oAuthAuthenticated, err = a.o.ValidateOAuth2AccessToken(w, r)
106 | if err != nil {
107 | return
108 | } else {
109 | // With or without OAuth, permit public access
110 | authenticated = true
111 | }
112 | // No OAuth2 means guaranteed denial of private access
113 | if !oAuthAuthenticated {
114 | return
115 | }
116 | // Determine if private access permitted by the granted scope.
117 | var ok bool
118 | ok, err = a.app.ScopePermitsPrivateGetInbox(t.GetScope())
119 | if err != nil {
120 | return
121 | } else {
122 | ctx := &util.Context{c}
123 | ctx.WithPrivateScope(ok)
124 | newCtx = ctx.Context
125 | }
126 | return
127 | }
128 |
--------------------------------------------------------------------------------
/ap/instance_actor_common.go:
--------------------------------------------------------------------------------
1 | // apcore is a server framework for implementing an ActivityPub application.
2 | // Copyright (C) 2019 Cory Slep
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package ap
18 |
19 | import (
20 | "context"
21 | "crypto/rsa"
22 | "net/http"
23 | "net/url"
24 |
25 | "github.com/go-fed/activity/pub"
26 | "github.com/go-fed/activity/streams/vocab"
27 | "github.com/go-fed/apcore/framework/conn"
28 | "github.com/go-fed/apcore/services"
29 | "github.com/go-fed/apcore/util"
30 | )
31 |
32 | var _ pub.CommonBehavior = &instanceActorCommonBehavior{}
33 |
34 | type instanceActorCommonBehavior struct {
35 | tc *conn.Controller
36 | db *Database
37 | pk *services.PrivateKeys
38 | }
39 |
40 | func newInstanceActorCommonBehavior(
41 | db *Database,
42 | tc *conn.Controller,
43 | pk *services.PrivateKeys) *instanceActorCommonBehavior {
44 | return &instanceActorCommonBehavior{
45 | tc: tc,
46 | db: db,
47 | pk: pk,
48 | }
49 | }
50 |
51 | func (a *instanceActorCommonBehavior) AuthenticateGetInbox(c context.Context, w http.ResponseWriter, r *http.Request) (newCtx context.Context, authenticated bool, err error) {
52 | authenticated = true
53 | newCtx = c
54 | return
55 | }
56 |
57 | func (a *instanceActorCommonBehavior) AuthenticateGetOutbox(c context.Context, w http.ResponseWriter, r *http.Request) (newCtx context.Context, authenticated bool, err error) {
58 | authenticated = true
59 | newCtx = c
60 | return
61 | }
62 |
63 | func (a *instanceActorCommonBehavior) GetOutbox(c context.Context, r *http.Request) (ocp vocab.ActivityStreamsOrderedCollectionPage, err error) {
64 | ctx := util.Context{c}
65 | // IfChange
66 | var outboxIRI *url.URL
67 | if outboxIRI, err = ctx.CompleteRequestURL(); err != nil {
68 | return
69 | }
70 | ocp, err = a.db.GetPublicOutbox(c, outboxIRI)
71 | // ThenChange(router.go)
72 | return
73 | }
74 |
75 | func (a *instanceActorCommonBehavior) NewTransport(c context.Context, actorBoxIRI *url.URL, gofedAgent string) (t pub.Transport, err error) {
76 | var privKey *rsa.PrivateKey
77 | var pubKeyURL *url.URL
78 | privKey, pubKeyURL, err = a.pk.GetUserHTTPSignatureKeyForInstanceActor(util.Context{c})
79 | if err != nil {
80 | return
81 | }
82 | return a.tc.Get(privKey, pubKeyURL.String())
83 | }
84 |
--------------------------------------------------------------------------------
/ap/instance_actor_s2s.go:
--------------------------------------------------------------------------------
1 | // apcore is a server framework for implementing an ActivityPub application.
2 | // Copyright (C) 2019 Cory Slep
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package ap
18 |
19 | import (
20 | "context"
21 | "net/http"
22 | "net/url"
23 |
24 | "github.com/go-fed/activity/pub"
25 | "github.com/go-fed/activity/streams/vocab"
26 | "github.com/go-fed/apcore/framework/config"
27 | "github.com/go-fed/apcore/framework/conn"
28 | "github.com/go-fed/apcore/services"
29 | "github.com/go-fed/apcore/util"
30 | )
31 |
32 | var _ pub.FederatingProtocol = &instanceActorFederatingBehavior{}
33 |
34 | type instanceActorFederatingBehavior struct {
35 | maxInboxForwardingDepth int
36 | maxDeliveryDepth int
37 | db *Database
38 | pk *services.PrivateKeys
39 | f *services.Followers
40 | tc *conn.Controller
41 | }
42 |
43 | func newInstanceActorFederatingBehavior(c *config.Config,
44 | db *Database,
45 | pk *services.PrivateKeys,
46 | f *services.Followers,
47 | tc *conn.Controller) *instanceActorFederatingBehavior {
48 | return &instanceActorFederatingBehavior{
49 | maxInboxForwardingDepth: c.ActivityPubConfig.MaxInboxForwardingRecursionDepth,
50 | maxDeliveryDepth: c.ActivityPubConfig.MaxDeliveryRecursionDepth,
51 | db: db,
52 | pk: pk,
53 | f: f,
54 | tc: tc,
55 | }
56 | }
57 |
58 | func (f *instanceActorFederatingBehavior) PostInboxRequestBodyHook(c context.Context, r *http.Request, activity pub.Activity) (out context.Context, err error) {
59 | ctx := &util.Context{c}
60 | ctx.WithActivity(activity)
61 | out = ctx.Context
62 | return
63 | }
64 |
65 | func (f *instanceActorFederatingBehavior) AuthenticatePostInbox(c context.Context, w http.ResponseWriter, r *http.Request) (out context.Context, authenticated bool, err error) {
66 | authenticated, err = verifyHttpSignatures(c, r, f.db, f.pk, f.tc)
67 | out = c
68 | return
69 | }
70 |
71 | func (f *instanceActorFederatingBehavior) Blocked(c context.Context, actorIRIs []*url.URL) (blocked bool, err error) {
72 | return
73 | }
74 |
75 | func (f *instanceActorFederatingBehavior) FederatingCallbacks(c context.Context) (wrapped pub.FederatingWrappedCallbacks, other []interface{}, err error) {
76 | wrapped = pub.FederatingWrappedCallbacks{
77 | OnFollow: pub.OnFollowDoNothing,
78 | }
79 | return
80 | }
81 |
82 | func (f *instanceActorFederatingBehavior) DefaultCallback(c context.Context, activity pub.Activity) error {
83 | activityIRI, err := pub.GetId(activity)
84 | if err != nil {
85 | return err
86 | }
87 | util.InfoLogger.Infof("Nothing to do for federated Activity of type %q: %s", activity.GetTypeName(), activityIRI)
88 | return nil
89 | }
90 |
91 | func (f *instanceActorFederatingBehavior) MaxInboxForwardingRecursionDepth(c context.Context) int {
92 | return f.maxInboxForwardingDepth
93 | }
94 |
95 | func (f *instanceActorFederatingBehavior) MaxDeliveryRecursionDepth(c context.Context) int {
96 | return f.maxDeliveryDepth
97 | }
98 |
99 | func (f *instanceActorFederatingBehavior) FilterForwarding(c context.Context, potentialRecipients []*url.URL, a pub.Activity) (filteredRecipients []*url.URL, err error) {
100 | ctx := util.Context{c}
101 | var actorIRI *url.URL
102 | actorIRI, err = ctx.ActorIRI()
103 | if err != nil {
104 | return
105 | }
106 | // Here we limit to only allow forwarding to the target user's
107 | // followers.
108 | var fc vocab.ActivityStreamsCollection
109 | fc, err = f.f.GetAllForActor(ctx, actorIRI)
110 | if err != nil {
111 | return
112 | }
113 | allowedRecipients := make(map[*url.URL]bool, 0)
114 | items := fc.GetActivityStreamsItems()
115 | if items != nil {
116 | for iter := items.Begin(); iter != items.End(); iter = iter.Next() {
117 | var id *url.URL
118 | id, err = pub.ToId(iter)
119 | if err != nil {
120 | return
121 | }
122 | allowedRecipients[id] = true
123 | }
124 | }
125 | for _, elem := range potentialRecipients {
126 | if has, ok := allowedRecipients[elem]; ok && has {
127 | filteredRecipients = append(filteredRecipients, elem)
128 | }
129 | }
130 | return
131 | }
132 |
133 | func (f *instanceActorFederatingBehavior) GetInbox(c context.Context, r *http.Request) (ocp vocab.ActivityStreamsOrderedCollectionPage, err error) {
134 | ctx := util.Context{c}
135 | // IfChange
136 | var inboxIRI *url.URL
137 | if inboxIRI, err = ctx.CompleteRequestURL(); err != nil {
138 | return
139 | }
140 | ocp, err = f.db.GetPublicInbox(c, inboxIRI)
141 | // ThenChange(router.go)
142 | return
143 | }
144 |
--------------------------------------------------------------------------------
/ap/util.go:
--------------------------------------------------------------------------------
1 | // apcore is a server framework for implementing an ActivityPub application.
2 | // Copyright (C) 2019 Cory Slep
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package ap
18 |
19 | import (
20 | "context"
21 | "crypto"
22 | "crypto/rsa"
23 | "crypto/x509"
24 | "encoding/json"
25 | "encoding/pem"
26 | "fmt"
27 | "net/http"
28 | "net/url"
29 |
30 | "github.com/go-fed/activity/pub"
31 | "github.com/go-fed/activity/streams"
32 | "github.com/go-fed/activity/streams/vocab"
33 | "github.com/go-fed/apcore/framework/conn"
34 | "github.com/go-fed/apcore/paths"
35 | "github.com/go-fed/apcore/services"
36 | "github.com/go-fed/apcore/util"
37 | "github.com/go-fed/httpsig"
38 | )
39 |
40 | type publicKeyer interface {
41 | GetW3IDSecurityV1PublicKey() vocab.W3IDSecurityV1PublicKeyProperty
42 | }
43 |
44 | func getPublicKeyFromResponse(c context.Context, b []byte, keyId *url.URL) (p crypto.PublicKey, err error) {
45 | m := make(map[string]interface{}, 0)
46 | err = json.Unmarshal(b, &m)
47 | if err != nil {
48 | return
49 | }
50 | var t vocab.Type
51 | t, err = streams.ToType(c, m)
52 | if err != nil {
53 | return
54 | }
55 | pker, ok := t.(publicKeyer)
56 | if !ok {
57 | err = fmt.Errorf("ActivityStreams type cannot be converted to one known to have publicKey property: %T", t)
58 | return
59 | }
60 | pkp := pker.GetW3IDSecurityV1PublicKey()
61 | if pkp == nil {
62 | err = fmt.Errorf("publicKey property is not provided")
63 | return
64 | }
65 | var pkpFound vocab.W3IDSecurityV1PublicKey
66 | for pkpIter := pkp.Begin(); pkpIter != pkp.End(); pkpIter = pkpIter.Next() {
67 | if !pkpIter.IsW3IDSecurityV1PublicKey() {
68 | continue
69 | }
70 | pkValue := pkpIter.Get()
71 | var pkId *url.URL
72 | pkId, err = pub.GetId(pkValue)
73 | if err != nil {
74 | return
75 | }
76 | if pkId.String() != keyId.String() {
77 | continue
78 | }
79 | pkpFound = pkValue
80 | break
81 | }
82 | if pkpFound == nil {
83 | err = fmt.Errorf("cannot find publicKey with id: %s", keyId)
84 | return
85 | }
86 | pkPemProp := pkpFound.GetW3IDSecurityV1PublicKeyPem()
87 | if pkPemProp == nil || !pkPemProp.IsXMLSchemaString() {
88 | err = fmt.Errorf("publicKeyPem property is not provided or it is not embedded as a value")
89 | return
90 | }
91 | pubKeyPem := pkPemProp.Get()
92 | var block *pem.Block
93 | block, _ = pem.Decode([]byte(pubKeyPem))
94 | if block == nil || block.Type != "PUBLIC KEY" {
95 | err = fmt.Errorf("could not decode publicKeyPem to PUBLIC KEY pem block type")
96 | return
97 | }
98 | p, err = x509.ParsePKIXPublicKey(block.Bytes)
99 | return
100 | }
101 |
102 | func verifyHttpSignatures(c context.Context,
103 | r *http.Request,
104 | db *Database,
105 | pk *services.PrivateKeys,
106 | tc *conn.Controller) (authenticated bool, err error) {
107 | // 1. Figure out what key we need to verify
108 | ctx := util.Context{c}
109 | var v httpsig.Verifier
110 | v, err = httpsig.NewVerifier(r)
111 | if err != nil {
112 | return
113 | }
114 | kId := v.KeyId()
115 | var kIdIRI *url.URL
116 | kIdIRI, err = url.Parse(kId)
117 | if err != nil {
118 | return
119 | }
120 | // 2. Get our user's credentials
121 | var userUUID paths.UUID
122 | userUUID, err = ctx.UserPathUUID()
123 | if err != nil {
124 | return
125 | }
126 | var privKey *rsa.PrivateKey
127 | var pubKeyURL *url.URL
128 | privKey, pubKeyURL, err = pk.GetUserHTTPSignatureKey(ctx, userUUID)
129 | if err != nil {
130 | return
131 | }
132 | pubKeyId := pubKeyURL.String()
133 | // 3. Fetch the public key of the other actor using our credentials
134 | tp, err := tc.Get(privKey, pubKeyId)
135 | if err != nil {
136 | return
137 | }
138 | var b []byte
139 | b, err = tp.Dereference(c, kIdIRI)
140 | if err != nil {
141 | return
142 | }
143 | pKey, err := getPublicKeyFromResponse(c, b, kIdIRI)
144 | if err != nil {
145 | return
146 | }
147 | // 4. Verify the other actor's key
148 | algo := tc.GetFirstAlgorithm()
149 | authenticated = nil == v.Verify(pKey, algo)
150 | return
151 | }
152 |
--------------------------------------------------------------------------------
/app/database.go:
--------------------------------------------------------------------------------
1 | // apcore is a server framework for implementing an ActivityPub application.
2 | // Copyright (C) 2019 Cory Slep
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package app
18 |
19 | import (
20 | "context"
21 | )
22 |
23 | type Database interface {
24 | Begin() TxBuilder
25 | }
26 |
27 | type TxBuilder interface {
28 | QueryOneRow(sql string, cb func(r SingleRow) error, args ...interface{})
29 | Query(sql string, cb func(r SingleRow) error, args ...interface{})
30 | ExecOneRow(sql string, args ...interface{})
31 | Exec(sql string, args ...interface{})
32 | Do(c context.Context) error
33 | }
34 |
35 | type SingleRow interface {
36 | Scan(dest ...interface{}) error
37 | }
38 |
--------------------------------------------------------------------------------
/app/framework.go:
--------------------------------------------------------------------------------
1 | // apcore is a server framework for implementing an ActivityPub application.
2 | // Copyright (C) 2019 Cory Slep
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package app
18 |
19 | import (
20 | "context"
21 | "net/http"
22 | "net/url"
23 |
24 | "github.com/go-fed/activity/streams/vocab"
25 | "github.com/go-fed/apcore/paths"
26 | )
27 |
28 | // Framework provides request-time hooks for use in handlers.
29 | type Framework interface {
30 | Context(r *http.Request) context.Context
31 |
32 | UserIRI(userUUID paths.UUID) *url.URL
33 |
34 | // CreateUser creates a new unprivileged user with the given username,
35 | // email, and password.
36 | //
37 | // If an error is returned, it can be checked using IsNotUniqueUsername
38 | // and IsNotUniqueEmail to show the error to the user.
39 | CreateUser(c context.Context, username, email, password string) (userID string, err error)
40 |
41 | // IsNotUniqueUsername returns true if the error returned from
42 | // CreateUser is due to the username not being unique.
43 | IsNotUniqueUsername(error) bool
44 |
45 | // IsNotUniqueEmail returns true if the error returned from CreateUser
46 | // is due to the email not being unique.
47 | IsNotUniqueEmail(error) bool
48 |
49 | // Validate attempts to obtain and validate the OAuth token or first
50 | // party credential in the request. This can be called in your handlers
51 | // at request-handing time.
52 | //
53 | // If an error is returned, both the token and authentication values
54 | // should be ignored.
55 | //
56 | // TODO: Scopes
57 | Validate(w http.ResponseWriter, r *http.Request) (userID paths.UUID, authenticated bool, err error)
58 |
59 | // Send will send an Activity or Object on behalf of the user.
60 | //
61 | // Note that a new ID is not needed on the activity and/or objects that
62 | // are being sent; they will be generated as needed.
63 | //
64 | // Calling Send when federation is disabled results in an error.
65 | Send(c context.Context, userID paths.UUID, toSend vocab.Type) error
66 |
67 | // SendAcceptFollow accepts the provided Follow on behalf of the user.
68 | //
69 | // Calling SendAcceptFollow when federation is disabled results in an
70 | // error.
71 | SendAcceptFollow(c context.Context, userID paths.UUID, followIRI *url.URL) error
72 |
73 | // SendRejectFollow rejects the provided Follow on behalf of the user.
74 | //
75 | // Calling SendRejectFollow when federation is disabled results in an
76 | // error.
77 | SendRejectFollow(c context.Context, userID paths.UUID, followIRI *url.URL) error
78 |
79 | Session(r *http.Request) (Session, error)
80 |
81 | // TODO: Determine if we need this.
82 | GetByIRI(c context.Context, id *url.URL) (vocab.Type, error)
83 |
84 | // Given a user ID, retrieves all follow requests that have not yet been
85 | // Accepted nor Rejected.
86 | OpenFollowRequests(c context.Context, userID paths.UUID) ([]vocab.ActivityStreamsFollow, error)
87 |
88 | // GetPrivileges accepts a pointer to an appPrivileges struct to read
89 | // from the database for the given user, and also returns whether that
90 | // user is an admin.
91 | GetPrivileges(c context.Context, userID paths.UUID, appPrivileges interface{}) (admin bool, err error)
92 | // SetPrivileges sets the given application privileges and admin status
93 | // for the given user.
94 | SetPrivileges(c context.Context, userID paths.UUID, admin bool, appPrivileges interface{}) error
95 | }
96 |
97 | type Session interface {
98 | UserID() (string, error)
99 | Set(string, interface{})
100 | Get(string) (interface{}, bool)
101 | Has(string) bool
102 | Delete(string)
103 | Save(*http.Request, http.ResponseWriter) error
104 | }
105 |
--------------------------------------------------------------------------------
/app/funcs.go:
--------------------------------------------------------------------------------
1 | // apcore is a server framework for implementing an ActivityPub application.
2 | // Copyright (C) 2019 Cory Slep
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package app
18 |
19 | import (
20 | "context"
21 | "net/http"
22 |
23 | "github.com/go-fed/activity/streams/vocab"
24 | )
25 |
26 | type AuthorizeFunc func(c context.Context, w http.ResponseWriter, r *http.Request, db Database) (permit bool, err error)
27 |
28 | type CollectionPageHandlerFunc func(http.ResponseWriter, *http.Request, vocab.ActivityStreamsCollectionPage)
29 |
30 | type VocabHandlerFunc func(http.ResponseWriter, *http.Request, vocab.Type)
31 |
--------------------------------------------------------------------------------
/app/paths.go:
--------------------------------------------------------------------------------
1 | // apcore is a server framework for implementing an ActivityPub application.
2 | // Copyright (C) 2019 Cory Slep
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package app
18 |
19 | // Paths is a set of endpoints for apcore handlers provided out of the box. It
20 | // allows applications to override defaults, for example in case localization
21 | // where "/login" can instead be "/{locale}/login".
22 | //
23 | // The Redirect fields are functions. They accept the current path and can
24 | // return a corresponding path. For example, when redirecting to the homepage
25 | // in a locale usecase, it may be passed "/de-DE/login" which means the function
26 | // then returns "/de-DE".
27 | //
28 | // A zero-value struct is valid and uses apcore defaults.
29 | type Paths struct {
30 | GetLogin string
31 | PostLogin string
32 | GetLogout string
33 | GetOAuth2Authorize string
34 | PostOAuth2Authorize string
35 | RedirectToHomepage func(string) string
36 | RedirectToLogin func(string) string
37 | }
38 |
39 | func (p Paths) getOrDefault(s, d string) string {
40 | if s == "" {
41 | return d
42 | }
43 | return s
44 | }
45 |
46 | func (p Paths) GetLoginPath() string {
47 | return p.getOrDefault(p.GetLogin, "/login")
48 | }
49 |
50 | func (p Paths) PostLoginPath() string {
51 | return p.getOrDefault(p.PostLogin, "/login")
52 | }
53 |
54 | func (p Paths) GetLogoutPath() string {
55 | return p.getOrDefault(p.GetLogout, "/logout")
56 | }
57 |
58 | func (p Paths) GetOAuth2AuthorizePath() string {
59 | return p.getOrDefault(p.GetOAuth2Authorize, "/oauth2/authorize")
60 | }
61 |
62 | func (p Paths) PostOAuth2AuthorizePath() string {
63 | return p.getOrDefault(p.PostOAuth2Authorize, "/oauth2/authorize")
64 | }
65 |
66 | func (p Paths) RedirectToHomepagePath(currentPath string) string {
67 | if p.RedirectToHomepage == nil {
68 | return "/"
69 | }
70 | return p.getOrDefault(p.RedirectToHomepage(currentPath), "/")
71 | }
72 |
73 | func (p Paths) RedirectToLoginPath(currentPath string) string {
74 | if p.RedirectToLogin == nil {
75 | return "/login"
76 | }
77 | return p.getOrDefault(p.RedirectToLogin(currentPath), "/")
78 | }
79 |
--------------------------------------------------------------------------------
/app/router.go:
--------------------------------------------------------------------------------
1 | // apcore is a server framework for implementing an ActivityPub application.
2 | // Copyright (C) 2019 Cory Slep
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package app
18 |
19 | import (
20 | "net/http"
21 |
22 | "github.com/gorilla/mux"
23 | )
24 |
25 | type Router interface {
26 | ActivityPubOnlyHandleFunc(path string, authFn AuthorizeFunc) Route
27 | ActivityPubAndWebHandleFunc(path string, authFn AuthorizeFunc, f func(http.ResponseWriter, *http.Request)) Route
28 | HandleAuthorizationRequest(path string) Route
29 | HandleAccessTokenRequest(path string) Route
30 | Get(name string) Route
31 | WebOnlyHandle(path string, handler http.Handler) Route
32 | WebOnlyHandleFunc(path string, f func(http.ResponseWriter, *http.Request)) Route
33 | Handle(path string, handler http.Handler) Route
34 | HandleFunc(path string, f func(http.ResponseWriter, *http.Request)) Route
35 | Headers(pairs ...string) Route
36 | Host(tpl string) Route
37 | Methods(methods ...string) Route
38 | Name(name string) Route
39 | NewRoute() Route
40 | Path(tpl string) Route
41 | PathPrefix(tpl string) Route
42 | Queries(pairs ...string) Route
43 | Schemes(schemes ...string) Route
44 | Use(mwf ...mux.MiddlewareFunc)
45 | Walk(walkFn mux.WalkFunc) error
46 | }
47 |
48 | type Route interface {
49 | ActivityPubOnlyHandleFunc(path string, authFn AuthorizeFunc) Route
50 | ActivityPubAndWebHandleFunc(path string, authFn AuthorizeFunc, f func(http.ResponseWriter, *http.Request)) Route
51 | HandleAuthorizationRequest(path string) Route
52 | HandleAccessTokenRequest(path string) Route
53 | WebOnlyHandler(path string, handler http.Handler) Route
54 | WebOnlyHandlerFunc(path string, f func(http.ResponseWriter, *http.Request)) Route
55 | Handler(handler http.Handler) Route
56 | HandlerFunc(f func(http.ResponseWriter, *http.Request)) Route
57 | Headers(pairs ...string) Route
58 | Host(tpl string) Route
59 | Methods(methods ...string) Route
60 | Name(name string) Route
61 | Path(tpl string) Route
62 | PathPrefix(tpl string) Route
63 | Queries(pairs ...string) Route
64 | Schemes(schemes ...string) Route
65 | Subrouter() Router
66 | }
67 |
--------------------------------------------------------------------------------
/app/software.go:
--------------------------------------------------------------------------------
1 | // apcore is a server framework for implementing an ActivityPub application.
2 | // Copyright (C) 2019 Cory Slep
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package app
18 |
19 | import (
20 | "fmt"
21 | )
22 |
23 | type Software struct {
24 | Name string
25 | UserAgent string
26 | MajorVersion int
27 | MinorVersion int
28 | PatchVersion int
29 | Repository string
30 | }
31 |
32 | func (s Software) String() string {
33 | return fmt.Sprintf(
34 | "%s (%s)",
35 | s.Name,
36 | s.Version())
37 | }
38 |
39 | func (s Software) Version() string {
40 | return fmt.Sprintf("%d.%d.%d",
41 | s.MajorVersion,
42 | s.MinorVersion,
43 | s.PatchVersion)
44 | }
45 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # apcore example
2 |
3 | This shows a very bare-bones server that has both S2S (Federation API) and C2S
4 | (Social API) enabled using the `apcore` framework.
5 |
6 | ## Requirements
7 |
8 | * A platform that can build `go-fed/activity` and `go-fed/apcore`.
9 | * A local Postgres server.
10 |
11 | ## Guide
12 |
13 | Once the example has been obtained, you will go through setting up the example
14 | like any administrator would when using the apcore framework. This lets all
15 | administrators benefit from a common workflow; improvements to this workflow are
16 | then shared across all applications, including this example one.
17 |
18 | The steps are:
19 |
20 | 0. Configuring the software (`configure`)
21 | 0. Initializing the database tables (`init-db`)
22 | 0. Creating an administrator's account (`init-admin`)
23 | 0. Launching the server (`serve`)
24 |
25 | ### Building the Binary
26 |
27 | Build the example's binary:
28 |
29 | `go install github.com/go-fed/apcore/example`
30 |
31 | This binary will be referred to as `example`.
32 |
33 | ### Preparing
34 |
35 | Note: These steps may require you to have a certificate (and key) to run a local
36 | server that serves `https` connections. A self-signed certificate is sufficient
37 | for development and non-public purposes. It can be generated using `openssl`:
38 |
39 | `openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365`
40 |
41 | If you have a domain name and certificate for that domain name, you can run this
42 | example application using that domain name to test federation with other
43 | instances.
44 |
45 | This application is meant to be a proof-of-concept, example, or a base for other
46 | applications, and *not* a permanent production server. **To enforce this, it will
47 | terminate itself after being up for one hour.**
48 |
49 | #### Configuring
50 |
51 | First, we must configure the software. This configuration can be changed later,
52 | but to take effect requires restarting the server. It mainly sets parameters
53 | for tuning resource usage, but applications can include other specific
54 | configuration parameters.
55 |
56 | To launch the guided flow, run:
57 |
58 | `example configure`
59 |
60 | which will write a `config.ini` file.
61 |
62 | #### Database Initialization
63 |
64 | Second, the configuration will be used to create the tables in a database. It
65 | requires connecting to the database.
66 |
67 | To do so, run:
68 |
69 | `example init-db`
70 |
71 | which will run `CREATE TABLE IF NOT EXISTS` commands.
72 |
73 | #### Administrator Account Creation
74 |
75 | Third, the first account needs to be created: an administrator account! It will
76 | be done in a guided flow, and requires connecting to the database.
77 |
78 | To launch the guided flow, run:
79 |
80 | `example init-admin`
81 |
82 | which will run several queries to create the administrator user.
83 |
84 | ### Launching The Example App Server
85 |
86 | The time has come! To launch the server, run the following command:
87 |
88 | `example serve`
89 |
90 | which can be exited with `ctl+c`.
91 |
92 | Once the server is up, it can be visited by going to `https://localhost/`.
93 |
94 | ## Features
95 |
96 | TODO
97 |
--------------------------------------------------------------------------------
/example/main.go:
--------------------------------------------------------------------------------
1 | // apcore is a server framework for implementing an ActivityPub application.
2 | // Copyright (C) 2019 Cory Slep
3 | //
4 | // This program is free software: you can redistribute it and/or modify
5 | // it under the terms of the GNU Affero General Public License as published by
6 | // the Free Software Foundation, either version 3 of the License, or
7 | // (at your option) any later version.
8 | //
9 | // This program is distributed in the hope that it will be useful,
10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 | // GNU Affero General Public License for more details.
13 | //
14 | // You should have received a copy of the GNU Affero General Public License
15 | // along with this program. If not, see .
16 |
17 | package main
18 |
19 | import (
20 | "github.com/go-fed/apcore"
21 | "github.com/go-fed/apcore/app"
22 | )
23 |
24 | func main() {
25 | // Build an instance of our struct that satisfies the Application
26 | // interface.
27 | //
28 | // Implementing the Application interface is where most of the work
29 | // to use the framework lies.
30 | //
31 | // go-fed/apcore provides a very quick-to-implement but vanilla
32 | // ActivityPub framework. It is a convenience layer on top of
33 | // go-fed/activity, which has the opposite philosophy: assume as little
34 | // as possible, provide more powerful but time-consuming interfaces to
35 | // satisfy.
36 | var a app.Application
37 | var e error
38 | a, e = newApplication("templates/*.tmpl")
39 | if e != nil {
40 | panic(e)
41 | }
42 | // Run the apcore framework.
43 | //
44 | // Depending on the command line flags chosen, an action can occur:
45 | // - Configuring the application and generating a config file
46 | // - Initializing a database
47 | // - Initializing an admin account in the database
48 | // - Launching the example App to serve live web & ActivityPub traffic
49 | //
50 | // All of these capabilities are supported by the framework out of the
51 | // box. Refer to the command line help for more details.
52 | apcore.Run(a)
53 | }
54 |
--------------------------------------------------------------------------------
/example/templates/auth.tmpl:
--------------------------------------------------------------------------------
1 | {{template "header.tmpl" .}}
2 |