File tree Expand file tree Collapse file tree 6 files changed +191
-0
lines changed Expand file tree Collapse file tree 6 files changed +191
-0
lines changed Original file line number Diff line number Diff line change
1
+ declare module "@sagi.io/cfw-jwt" {
2
+ type PrivateKey = string ;
3
+ type AppId = number ;
4
+ type Expiration = number ;
5
+ type Token = string ;
6
+
7
+ type Result = {
8
+ appId : AppId ;
9
+ expiration : Expiration ;
10
+ token : Token ;
11
+ } ;
12
+
13
+ type Payload = {
14
+ iat : number ;
15
+ exp : number ;
16
+ iss : number ;
17
+ } ;
18
+
19
+ type GetTokenOptions = {
20
+ privateKeyPEM : PrivateKey ;
21
+ payload : Payload ;
22
+ alg : "RS256" ;
23
+ cryptoImpl : Crypto ;
24
+ headerAdditions : { } ;
25
+ } ;
26
+
27
+ export function getToken ( options : GetTokenOptions ) : Token ;
28
+ }
Original file line number Diff line number Diff line change
1
+ import { GetTokenOptions , Token } from "./types" ;
2
+ import {
3
+ getEncodedMessage ,
4
+ getDERfromPEM ,
5
+ string2ArrayBuffer ,
6
+ base64encode
7
+ } from "./utils" ;
8
+
9
+ export const getToken = async ( {
10
+ privateKey,
11
+ payload
12
+ } : GetTokenOptions ) : Promise < Token > => {
13
+ // WebCrypto only supports PKCS#8, unfortunately
14
+ if ( / B E G I N R S A P R I V A T E K E Y / . test ( privateKey ) ) {
15
+ throw new Error (
16
+ "[universal-github-app-jwt] Private Key is in PKCS#1 format, but only PKCS#8 is supported. See https://github.com/gr2m/universal-github-app-jwt#readme"
17
+ ) ;
18
+ }
19
+
20
+ const algorithm = {
21
+ name : "RSASSA-PKCS1-v1_5" ,
22
+ hash : { name : "SHA-256" }
23
+ } ;
24
+ const header = { alg : "RS256" , typ : "JWT" } ;
25
+
26
+ const privateKeyDER = getDERfromPEM ( privateKey ) ;
27
+ const importedKey = await crypto . subtle . importKey (
28
+ "pkcs8" ,
29
+ privateKeyDER ,
30
+ algorithm ,
31
+ false ,
32
+ [ "sign" ]
33
+ ) ;
34
+
35
+ const encodedMessage = getEncodedMessage ( header , payload ) ;
36
+ const encodedMessageArrBuf = string2ArrayBuffer ( encodedMessage ) ;
37
+
38
+ const signatureArrBuf = await crypto . subtle . sign (
39
+ algorithm . name ,
40
+ importedKey ,
41
+ encodedMessageArrBuf
42
+ ) ;
43
+
44
+ const encodedSignature = base64encode ( signatureArrBuf ) ;
45
+
46
+ return `${ encodedMessage } .${ encodedSignature } ` ;
47
+ } ;
Original file line number Diff line number Diff line change
1
+ import jsonwebtoken from "jsonwebtoken" ;
2
+
3
+ import { GetTokenOptions , Token } from "./types" ;
4
+
5
+ export async function getToken ( {
6
+ privateKey,
7
+ payload
8
+ } : GetTokenOptions ) : Promise < Token > {
9
+ return jsonwebtoken . sign ( payload , privateKey , {
10
+ algorithm : "RS256"
11
+ } ) ;
12
+ }
Original file line number Diff line number Diff line change
1
+ import { getToken } from "./get-token" ;
2
+
3
+ import { Options , Result } from "./types" ;
4
+
5
+ export async function githubAppJwt ( {
6
+ id,
7
+ privateKey
8
+ } : Options ) : Promise < Result > {
9
+ // When creating a JSON Web Token, it sets the "issued at time" (iat) to 30s
10
+ // in the past as we have seen people running situations where the GitHub API
11
+ // claimed the iat would be in future. It turned out the clocks on the
12
+ // different machine were not in sync.
13
+ const now = Math . floor ( Date . now ( ) / 1000 ) - 30 ;
14
+ const expiration = now + 60 * 10 ; // JWT expiration time (10 minute maximum)
15
+
16
+ const payload = {
17
+ iat : now , // Issued at time
18
+ exp : expiration ,
19
+ iss : id
20
+ } ;
21
+
22
+ const token = await getToken ( {
23
+ privateKey,
24
+ payload
25
+ } ) ;
26
+
27
+ return {
28
+ appId : id ,
29
+ expiration,
30
+ token
31
+ } ;
32
+ }
Original file line number Diff line number Diff line change
1
+ export type PrivateKey = string ;
2
+ export type AppId = number ;
3
+ export type Expiration = number ;
4
+ export type Token = string ;
5
+
6
+ export type Options = {
7
+ id : AppId ;
8
+ privateKey : PrivateKey ;
9
+ crypto ?: Crypto ;
10
+ } ;
11
+
12
+ export type Result = {
13
+ appId : AppId ;
14
+ expiration : Expiration ;
15
+ token : Token ;
16
+ } ;
17
+
18
+ export type Payload = {
19
+ iat : number ;
20
+ exp : number ;
21
+ iss : number ;
22
+ } ;
23
+
24
+ export type GetTokenOptions = {
25
+ privateKey : PrivateKey ;
26
+ payload : Payload ;
27
+ } ;
Original file line number Diff line number Diff line change
1
+ export function string2ArrayBuffer ( str : string ) {
2
+ const buf = new ArrayBuffer ( str . length ) ;
3
+ const bufView = new Uint8Array ( buf ) ;
4
+ for ( let i = 0 , strLen = str . length ; i < strLen ; i ++ ) {
5
+ bufView [ i ] = str . charCodeAt ( i ) ;
6
+ }
7
+ return buf ;
8
+ }
9
+
10
+ export function getDERfromPEM ( pem : string ) : ArrayBuffer {
11
+ const pemB64 = pem
12
+ . trim ( )
13
+ . split ( "\n" )
14
+ . slice ( 1 , - 1 ) // Remove the --- BEGIN / END PRIVATE KEY ---
15
+ . join ( "" ) ;
16
+
17
+ const decoded = atob ( pemB64 ) ;
18
+ return string2ArrayBuffer ( decoded ) ;
19
+ }
20
+
21
+ export function getEncodedMessage ( header : object , payload : object ) : string {
22
+ return `${ base64encodeJSON ( header ) } .${ base64encodeJSON ( payload ) } ` ;
23
+ }
24
+
25
+ export function base64encode ( buffer : ArrayBuffer ) : string {
26
+ var binary = "" ;
27
+ var bytes = new Uint8Array ( buffer ) ;
28
+ var len = bytes . byteLength ;
29
+ for ( var i = 0 ; i < len ; i ++ ) {
30
+ binary += String . fromCharCode ( bytes [ i ] ) ;
31
+ }
32
+
33
+ return fromBase64 ( btoa ( binary ) ) ;
34
+ }
35
+
36
+ function fromBase64 ( base64 : string ) : string {
37
+ return base64
38
+ . replace ( / = / g, "" )
39
+ . replace ( / \+ / g, "-" )
40
+ . replace ( / \/ / g, "_" ) ;
41
+ }
42
+
43
+ function base64encodeJSON ( obj : object ) {
44
+ return fromBase64 ( btoa ( JSON . stringify ( obj ) ) ) ;
45
+ }
You can’t perform that action at this time.
0 commit comments