-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #8 from SwimResults/develop
simple notification request, apns config
- Loading branch information
Showing
17 changed files
with
454 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,10 @@ | |
*.so | ||
*.dylib | ||
|
||
# private apns for apns | ||
*.p8 | ||
!AuthKey.p8 | ||
|
||
# Test binary, built with `go test -c` | ||
*.test | ||
|
||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package apns | ||
|
||
import ( | ||
"crypto/ecdsa" | ||
"crypto/x509" | ||
"encoding/pem" | ||
"errors" | ||
"github.com/dgrijalva/jwt-go" | ||
"os" | ||
"sync" | ||
"time" | ||
) | ||
|
||
const ( | ||
// Timeout is the period of time in seconds that a token is valid for. | ||
// If the timestamp for token issue is not within the last hour, APNs | ||
// rejects subsequent push messages. This is set to under an hour so that | ||
// we generate a new token before the existing one expires. | ||
Timeout = 3000 | ||
) | ||
|
||
// Possible errors when parsing a .p8 file. | ||
var ( | ||
ErrAuthKeyNotPem = errors.New("token: AuthKey must be a valid .p8 PEM file") | ||
ErrAuthKeyNotECDSA = errors.New("token: AuthKey must be of type ecdsa.PrivateKey") | ||
ErrAuthKeyNil = errors.New("token: AuthKey was nil") | ||
) | ||
|
||
// Token represents an Apple Provider Authentication Token (JSON Web Token). | ||
type Token struct { | ||
sync.Mutex | ||
AuthKey *ecdsa.PrivateKey | ||
KeyID string | ||
TeamID string | ||
IssuedAt int64 | ||
Bearer string | ||
} | ||
|
||
// AuthKeyFromFile loads a .p8 certificate from a local file and returns a | ||
// *ecdsa.PrivateKey. | ||
func AuthKeyFromFile(filename string) (*ecdsa.PrivateKey, error) { | ||
bytes, err := os.ReadFile(filename) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return AuthKeyFromBytes(bytes) | ||
} | ||
|
||
// AuthKeyFromBytes loads a .p8 certificate from an in memory byte array and | ||
// returns an *ecdsa.PrivateKey. | ||
func AuthKeyFromBytes(bytes []byte) (*ecdsa.PrivateKey, error) { | ||
block, _ := pem.Decode(bytes) | ||
if block == nil { | ||
return nil, ErrAuthKeyNotPem | ||
} | ||
key, err := x509.ParsePKCS8PrivateKey(block.Bytes) | ||
if err != nil { | ||
return nil, err | ||
} | ||
if pk, ok := key.(*ecdsa.PrivateKey); ok { | ||
return pk, nil | ||
} | ||
return nil, ErrAuthKeyNotECDSA | ||
} | ||
|
||
// GenerateIfExpired checks to see if the token is about to expire and | ||
// generates a new token. | ||
func (t *Token) GenerateIfExpired() (bearer string) { | ||
t.Lock() | ||
defer t.Unlock() | ||
if t.Expired() { | ||
t.Generate() | ||
} | ||
return t.Bearer | ||
} | ||
|
||
// Expired checks to see if the token has expired. | ||
func (t *Token) Expired() bool { | ||
return time.Now().Unix() >= (t.IssuedAt + Timeout) | ||
} | ||
|
||
// Generate creates a new token. | ||
func (t *Token) Generate() (bool, error) { | ||
if t.AuthKey == nil { | ||
return false, ErrAuthKeyNil | ||
} | ||
issuedAt := time.Now().Unix() | ||
jwtToken := &jwt.Token{ | ||
Header: map[string]interface{}{ | ||
"alg": "ES256", | ||
"kid": t.KeyID, | ||
}, | ||
Claims: jwt.MapClaims{ | ||
"iss": t.TeamID, | ||
"iat": issuedAt, | ||
}, | ||
Method: jwt.SigningMethodES256, | ||
} | ||
bearer, err := jwtToken.SignedString(t.AuthKey) | ||
if err != nil { | ||
return false, err | ||
} | ||
t.IssuedAt = issuedAt | ||
t.Bearer = bearer | ||
return true, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package apns | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"github.com/swimresults/user-service/model" | ||
"os" | ||
"sync" | ||
) | ||
|
||
var token Token | ||
|
||
func GetToken() string { | ||
token.GenerateIfExpired() | ||
return token.Bearer | ||
} | ||
|
||
func Init() { | ||
|
||
authKey, err := AuthKeyFromFile("config/apns/AuthKey.p8") | ||
if err != nil { | ||
println("failed reading apns token file") | ||
} | ||
|
||
dat, err1 := os.ReadFile("config/apns/token_config.json") | ||
if err1 != nil { | ||
println(err1.Error()) | ||
return | ||
} | ||
|
||
var tokenConfig model.ApnsTokenConfig | ||
|
||
err = json.Unmarshal(dat, &tokenConfig) | ||
if err != nil { | ||
println(err.Error()) | ||
return | ||
} | ||
fmt.Printf("set token config to: key: '%s'; team: '%s'\n", tokenConfig.KeyId, tokenConfig.TeamId) | ||
|
||
token = Token{ | ||
Mutex: sync.Mutex{}, | ||
AuthKey: authKey, | ||
KeyID: tokenConfig.KeyId, | ||
TeamID: tokenConfig.TeamId, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
-----BEGIN PRIVATE KEY----- | ||
### EXAMPLE FILE ### | ||
-----END PRIVATE KEY----- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"key_id": "", | ||
"team_id": "" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package controller | ||
|
||
import ( | ||
"github.com/gin-gonic/gin" | ||
"github.com/swimresults/user-service/dto" | ||
"github.com/swimresults/user-service/service" | ||
"net/http" | ||
) | ||
|
||
func notificationController() { | ||
|
||
router.POST("/notification/test/:device", sendTestNotification) | ||
router.POST("/notification/:device", sendNotification) | ||
|
||
router.OPTIONS("/notification/test/:device", okay) | ||
router.OPTIONS("/notification/:device", okay) | ||
} | ||
|
||
func sendTestNotification(c *gin.Context) { | ||
|
||
if failIfNotRoot(c) { | ||
return | ||
} | ||
|
||
device := c.Param("device") | ||
|
||
err := service.SendTestPushNotification(device) | ||
if err != nil { | ||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": err.Error()}) | ||
return | ||
} | ||
|
||
c.Status(http.StatusOK) | ||
} | ||
|
||
func sendNotification(c *gin.Context) { | ||
|
||
if failIfNotRoot(c) { | ||
return | ||
} | ||
|
||
device := c.Param("device") | ||
|
||
var request dto.NotificationRequestDto | ||
if err := c.BindJSON(&request); err != nil { | ||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": err.Error()}) | ||
return | ||
} | ||
|
||
apnsId, body, status, err := service.SendPushNotification(device, request.Title, request.Subtitle, request.Message) | ||
if err != nil { | ||
c.IndentedJSON(http.StatusInternalServerError, gin.H{"message": err.Error()}) | ||
return | ||
} | ||
|
||
c.IndentedJSON(status, dto.NotificationResponseDto{ | ||
ApnsId: apnsId, | ||
Body: body, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package dto | ||
|
||
type NotificationRequestDto struct { | ||
Title string `json:"title"` | ||
Subtitle string `json:"subtitle"` | ||
Message string `json:"message"` | ||
} | ||
|
||
type NotificationResponseDto struct { | ||
ApnsId string `json:"apns_id"` | ||
Body string `json:"body"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,9 @@ | ||
package dto | ||
|
||
import "github.com/swimresults/user-service/model" | ||
|
||
type RegisterNotificationUserRequestDto struct { | ||
Token string `json:"token,omitempty"` | ||
Token string `json:"token,omitempty"` | ||
Device model.Device `json:"device,omitempty"` | ||
Settings model.NotificationSettings `json:"settings,omitempty"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package model | ||
|
||
type ApnsTokenConfig struct { | ||
KeyId string `json:"key_id"` | ||
TeamId string `json:"team_id"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package model | ||
|
||
type Device struct { | ||
Name string `json:"name" bson:"name"` | ||
Model string `json:"model" bson:"model"` | ||
LocalizedModel string `json:"localized_model" bson:"localized_model"` | ||
SystemName string `json:"system_name" bson:"system_name"` | ||
SystemVersion string `json:"system_version" bson:"system_version"` | ||
Type string `json:"type" bson:"type"` | ||
UISize string `json:"ui_size" bson:"ui_size"` | ||
Language string `json:"language" bson:"language"` | ||
AppVersion string `json:"app_version" bson:"app_version"` | ||
} |
Oops, something went wrong.