Skip to content

Commit 2323ea7

Browse files
committed
feat: Initial open source commit
0 parents  commit 2323ea7

File tree

133 files changed

+16622
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

133 files changed

+16622
-0
lines changed

.github/workflows/check.yml

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Lint and Test
2+
3+
on: push
4+
5+
jobs:
6+
check:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- name: Get sources
10+
uses: actions/checkout@v3
11+
12+
- name: Set up Go 1.18
13+
uses: actions/setup-go@v3
14+
with:
15+
go-version: '1.18'
16+
17+
- name: Run golangci-lint
18+
uses: golangci/golangci-lint-action@v3
19+
with:
20+
version: v1.50.0
21+
args: --timeout=180s
22+
skip-cache: true
23+
24+
- name: Run tests
25+
run: go test -v ./...
26+
27+
- name: Run tests with race check
28+
run: go test -v -race ./...

CONTRIBUTING.md

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Contribution Policy
2+
3+
By making a contribution to this project:
4+
5+
1. I assign any and all copyright related to the contribution to Proton AG;
6+
2. I certify that the contribution was created in whole by me;
7+
3. I understand and agree that this project and the contribution are public
8+
and that a record of the contribution (including all personal information I
9+
submit with it) is maintained indefinitely and may be redistributed with
10+
this project or the open source license(s) involved.

LICENSE

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2020 James Houlahan
4+
Copyright (c) 2022 Proton AG
5+
6+
Permission is hereby granted, free of charge, to any person obtaining a copy
7+
of this software and associated documentation files (the "Software"), to deal
8+
in the Software without restriction, including without limitation the rights
9+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
copies of the Software, and to permit persons to whom the Software is
11+
furnished to do so, subject to the following conditions:
12+
13+
The above copyright notice and this permission notice shall be included in all
14+
copies or substantial portions of the Software.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22+
SOFTWARE.

README.md

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Go Proton API
2+
3+
<a href="https://github.com/ProtonMail/go-proton-api/actions/workflows/check.yml"><img src="https://github.com/ProtonMail/go-proton-api/actions/workflows/check.yml/badge.svg?branch=master" alt="CI Status"></a>
4+
<a href="https://pkg.go.dev/github.com/ProtonMail/go-proton-api"><img src="https://pkg.go.dev/badge/github.com/ProtonMail/go-proton-api" alt="GoDoc"></a>
5+
<a href="https://goreportcard.com/report/ProtonMail/go-proton-api"><img src="https://goreportcard.com/badge/ProtonMail/go-proton-api" alt="Go Report Card"></a>
6+
<a href="LICENSE"><img src="https://img.shields.io/github/license/ProtonMail/go-proton-api.svg" alt="License"></a>
7+
8+
This repository holds Go Proton API, a Go library implementing a client and development server for (a subset of) the Proton REST API.
9+
10+
The license can be found in the [LICENSE](./LICENSE) file.
11+
12+
For the contribution policy, see [CONTRIBUTING](./CONTRIBUTING.md).
13+
14+
## Environment variables
15+
16+
Most of the integration tests run locally. The ones that interact with Proton servers require the following environment variables set:
17+
18+
- ```GO_PROTON_API_TEST_USERNAME```
19+
- ```GO_PROTON_API_TEST_PASSWORD```
20+
21+
## Contribution
22+
23+
The library is maintained by Proton AG, and is not actively looking for contributors.

address.go

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package proton
2+
3+
import (
4+
"context"
5+
6+
"github.com/go-resty/resty/v2"
7+
"golang.org/x/exp/slices"
8+
)
9+
10+
func (c *Client) GetAddresses(ctx context.Context) ([]Address, error) {
11+
var res struct {
12+
Addresses []Address
13+
}
14+
15+
if err := c.do(ctx, func(r *resty.Request) (*resty.Response, error) {
16+
return r.SetResult(&res).Get("/core/v4/addresses")
17+
}); err != nil {
18+
return nil, err
19+
}
20+
21+
slices.SortFunc(res.Addresses, func(a, b Address) bool {
22+
return a.Order < b.Order
23+
})
24+
25+
return res.Addresses, nil
26+
}
27+
28+
func (c *Client) GetAddress(ctx context.Context, addressID string) (Address, error) {
29+
var res struct {
30+
Address Address
31+
}
32+
33+
if err := c.do(ctx, func(r *resty.Request) (*resty.Response, error) {
34+
return r.SetResult(&res).Get("/core/v4/addresses/" + addressID)
35+
}); err != nil {
36+
return Address{}, err
37+
}
38+
39+
return res.Address, nil
40+
}
41+
42+
func (c *Client) OrderAddresses(ctx context.Context, req OrderAddressesReq) error {
43+
return c.do(ctx, func(r *resty.Request) (*resty.Response, error) {
44+
return r.SetBody(req).Put("/core/v4/addresses/order")
45+
})
46+
}

address_types.go

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package proton
2+
3+
type Address struct {
4+
ID string
5+
Email string
6+
7+
Send Bool
8+
Receive Bool
9+
Status AddressStatus
10+
11+
Order int
12+
DisplayName string
13+
14+
Keys Keys
15+
}
16+
17+
type OrderAddressesReq struct {
18+
AddressIDs []string
19+
}
20+
21+
type AddressStatus int
22+
23+
const (
24+
AddressStatusDisabled AddressStatus = iota
25+
AddressStatusEnabled
26+
AddressStatusDeleting
27+
)

atomic.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package proton
2+
3+
import "sync/atomic"
4+
5+
type atomicUint64 struct {
6+
v uint64
7+
}
8+
9+
func (x *atomicUint64) Load() uint64 { return atomic.LoadUint64(&x.v) }
10+
11+
func (x *atomicUint64) Store(val uint64) { atomic.StoreUint64(&x.v, val) }
12+
13+
func (x *atomicUint64) Swap(new uint64) (old uint64) { return atomic.SwapUint64(&x.v, new) }
14+
15+
func (x *atomicUint64) Add(delta uint64) (new uint64) { return atomic.AddUint64(&x.v, delta) }
16+
17+
type atomicBool struct {
18+
v uint32
19+
}
20+
21+
func (x *atomicBool) Load() bool { return atomic.LoadUint32(&x.v) != 0 }
22+
23+
func (x *atomicBool) Store(val bool) { atomic.StoreUint32(&x.v, b32(val)) }
24+
25+
func (x *atomicBool) Swap(new bool) (old bool) { return atomic.SwapUint32(&x.v, b32(new)) != 0 }
26+
27+
func b32(b bool) uint32 {
28+
if b {
29+
return 1
30+
}
31+
32+
return 0
33+
}

attachment.go

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package proton
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"fmt"
7+
"io"
8+
9+
"github.com/ProtonMail/gopenpgp/v2/crypto"
10+
"github.com/go-resty/resty/v2"
11+
)
12+
13+
func (c *Client) GetAttachment(ctx context.Context, attachmentID string) ([]byte, error) {
14+
return c.attPool().ProcessOne(ctx, attachmentID)
15+
}
16+
17+
func (c *Client) UploadAttachment(ctx context.Context, addrKR *crypto.KeyRing, req CreateAttachmentReq) (Attachment, error) {
18+
var res struct {
19+
Attachment Attachment
20+
}
21+
22+
sig, err := addrKR.SignDetached(crypto.NewPlainMessage(req.Body))
23+
if err != nil {
24+
return Attachment{}, fmt.Errorf("failed to sign attachment: %w", err)
25+
}
26+
27+
enc, err := addrKR.EncryptAttachment(crypto.NewPlainMessage(req.Body), req.Filename)
28+
if err != nil {
29+
return Attachment{}, fmt.Errorf("failed to encrypt attachment: %w", err)
30+
}
31+
32+
if err := c.do(ctx, func(r *resty.Request) (*resty.Response, error) {
33+
return r.SetResult(&res).
34+
SetMultipartFormData(map[string]string{
35+
"MessageID": req.MessageID,
36+
"Filename": req.Filename,
37+
"MIMEType": string(req.MIMEType),
38+
"Disposition": string(req.Disposition),
39+
"ContentID": req.ContentID,
40+
}).
41+
SetMultipartFields(
42+
&resty.MultipartField{
43+
Param: "KeyPackets",
44+
FileName: "blob",
45+
ContentType: "application/octet-stream",
46+
Reader: bytes.NewReader(enc.KeyPacket),
47+
},
48+
&resty.MultipartField{
49+
Param: "DataPacket",
50+
FileName: "blob",
51+
ContentType: "application/octet-stream",
52+
Reader: bytes.NewReader(enc.DataPacket),
53+
},
54+
&resty.MultipartField{
55+
Param: "Signature",
56+
FileName: "blob",
57+
ContentType: "application/octet-stream",
58+
Reader: bytes.NewReader(sig.GetBinary()),
59+
},
60+
).
61+
Post("/mail/v4/attachments")
62+
}); err != nil {
63+
return Attachment{}, err
64+
}
65+
66+
return res.Attachment, nil
67+
}
68+
69+
func (c *Client) getAttachment(ctx context.Context, attachmentID string) ([]byte, error) {
70+
res, err := c.doRes(ctx, func(r *resty.Request) (*resty.Response, error) {
71+
return r.SetDoNotParseResponse(true).Get("/mail/v4/attachments/" + attachmentID)
72+
})
73+
if err != nil {
74+
return nil, err
75+
}
76+
defer res.RawBody().Close()
77+
78+
return io.ReadAll(res.RawBody())
79+
}

attachment_types.go

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package proton
2+
3+
import (
4+
"github.com/ProtonMail/gluon/rfc822"
5+
)
6+
7+
type Attachment struct {
8+
ID string
9+
10+
Name string
11+
Size int64
12+
MIMEType rfc822.MIMEType
13+
Disposition Disposition
14+
Headers Headers
15+
16+
KeyPackets string
17+
Signature string
18+
}
19+
20+
type Disposition string
21+
22+
const (
23+
InlineDisposition Disposition = "inline"
24+
AttachmentDisposition Disposition = "attachment"
25+
)
26+
27+
type CreateAttachmentReq struct {
28+
MessageID string
29+
30+
Filename string
31+
MIMEType rfc822.MIMEType
32+
Disposition Disposition
33+
ContentID string
34+
35+
Body []byte
36+
}

auth.go

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package proton
2+
3+
import (
4+
"context"
5+
6+
"github.com/go-resty/resty/v2"
7+
)
8+
9+
func (c *Client) Auth2FA(ctx context.Context, req Auth2FAReq) error {
10+
return c.do(ctx, func(r *resty.Request) (*resty.Response, error) {
11+
return r.SetBody(req).Post("/core/v4/auth/2fa")
12+
})
13+
}
14+
15+
func (c *Client) AuthDelete(ctx context.Context) error {
16+
return c.do(ctx, func(r *resty.Request) (*resty.Response, error) {
17+
return r.Delete("/core/v4/auth")
18+
})
19+
}
20+
21+
func (c *Client) AuthSessions(ctx context.Context) ([]AuthSession, error) {
22+
var res struct {
23+
Sessions []AuthSession
24+
}
25+
26+
if err := c.do(ctx, func(r *resty.Request) (*resty.Response, error) {
27+
return r.SetResult(&res).Get("/auth/v4/sessions")
28+
}); err != nil {
29+
return nil, err
30+
}
31+
32+
return res.Sessions, nil
33+
}
34+
35+
func (c *Client) AuthRevoke(ctx context.Context, authUID string) error {
36+
return c.do(ctx, func(r *resty.Request) (*resty.Response, error) {
37+
return r.Delete("/auth/v4/sessions/" + authUID)
38+
})
39+
}
40+
41+
func (c *Client) AuthRevokeAll(ctx context.Context) error {
42+
return c.do(ctx, func(r *resty.Request) (*resty.Response, error) {
43+
return r.Delete("/auth/v4/sessions")
44+
})
45+
}

0 commit comments

Comments
 (0)