1+ import type { OAuth2Adapter } from "adminforth" ;
2+
3+ export class AdminForthAdapterGithubOauth2 implements OAuth2Adapter {
4+ private clientID : string ;
5+ private clientSecret : string ;
6+ private redirectUri : string ;
7+
8+ constructor ( options : {
9+ clientID : string ;
10+ clientSecret : string ;
11+ redirectUri : string ;
12+ } ) {
13+ this . clientID = options . clientID ;
14+ this . clientSecret = options . clientSecret ;
15+ this . redirectUri = options . redirectUri ;
16+ }
17+
18+ getAuthUrl ( ) : string {
19+ const params = new URLSearchParams ( {
20+ client_id : this . clientID ,
21+ redirect_uri : this . redirectUri ,
22+ scope : 'user:email read:user' ,
23+ allow_signup : 'true' ,
24+ provider : this . constructor . name
25+ } ) ;
26+ const url = `https://github.com/login/oauth/authorize?${ params . toString ( ) } ` ;
27+ return url ;
28+ }
29+
30+ async getTokenFromCode ( code : string ) : Promise < { email : string ; name ?: string ; picture ?: string } > {
31+ console . log ( 'Getting token from code:' , code ) ;
32+
33+ // Exchange code for token
34+ const tokenResponse = await fetch ( 'https://github.com/login/oauth/access_token' , {
35+ method : 'POST' ,
36+ headers : {
37+ 'Accept' : 'application/json' ,
38+ 'Content-Type' : 'application/json'
39+ } ,
40+ body : JSON . stringify ( {
41+ code,
42+ client_id : this . clientID ,
43+ client_secret : this . clientSecret ,
44+ redirect_uri : this . redirectUri ,
45+ } ) ,
46+ } ) ;
47+
48+ const tokenData = await tokenResponse . json ( ) ;
49+
50+ if ( tokenData . error ) {
51+ console . error ( 'Token error:' , tokenData ) ;
52+ throw new Error ( tokenData . error_description || tokenData . error ) ;
53+ }
54+
55+ // Get user info using access token
56+ const userResponse = await fetch ( 'https://api.github.com/user' , {
57+ headers : {
58+ 'Authorization' : `Bearer ${ tokenData . access_token } ` ,
59+ 'Accept' : 'application/vnd.github.v3+json'
60+ } ,
61+ } ) ;
62+
63+ const userData = await userResponse . json ( ) ;
64+
65+ if ( userData . error ) {
66+ throw new Error ( userData . error_description || userData . error ) ;
67+ }
68+
69+ // If email is not public, fetch email separately
70+ if ( ! userData . email ) {
71+ const emailResponse = await fetch ( 'https://api.github.com/user/emails' , {
72+ headers : {
73+ 'Authorization' : `Bearer ${ tokenData . access_token } ` ,
74+ 'Accept' : 'application/vnd.github.v3+json'
75+ } ,
76+ } ) ;
77+
78+ const emails = await emailResponse . json ( ) ;
79+ const primaryEmail = emails . find ( email => email . primary ) ;
80+ userData . email = primaryEmail ?. email ;
81+ }
82+
83+ return {
84+ email : userData . email ,
85+ } ;
86+ }
87+ getIcon ( ) : string {
88+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 98 98"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#24292f"/></svg>` ;
89+ }
90+ }
0 commit comments