Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commita9c89e3

Browse files
committed
github oauth2 device flow frontend
1 parent2896490 commita9c89e3

File tree

7 files changed

+321
-105
lines changed

7 files changed

+321
-105
lines changed

‎site/src/api/api.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1605,6 +1605,29 @@ class ApiMethods {
16051605
returnresp.data;
16061606
};
16071607

1608+
getOAuth2GitHubDeviceFlowCallback=async(
1609+
code:string,
1610+
state:string,
1611+
):Promise<TypesGen.OAuth2DeviceFlowCallbackResponse>=>{
1612+
constresp=awaitthis.axios.get(
1613+
`/api/v2/users/oauth2/github/callback?code=${code}&state=${state}`,
1614+
);
1615+
// sanity check
1616+
if(
1617+
typeofresp.data!=="object"||
1618+
typeofresp.data.redirect_url!=="string"
1619+
){
1620+
console.error("Invalid response from OAuth2 GitHub callback",resp);
1621+
thrownewError("Invalid response from OAuth2 GitHub callback");
1622+
}
1623+
returnresp.data;
1624+
};
1625+
1626+
getOAuth2GitHubDevice=async():Promise<TypesGen.ExternalAuthDevice>=>{
1627+
constresp=awaitthis.axios.get("/api/v2/users/oauth2/github/device");
1628+
returnresp.data;
1629+
};
1630+
16081631
getOAuth2ProviderApps=async(
16091632
filter?:TypesGen.OAuth2ProviderAppFilter,
16101633
):Promise<TypesGen.OAuth2ProviderApp[]>=>{

‎site/src/api/queries/oauth2.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,20 @@ const userAppsKey = (userId: string) => appsKey.concat(userId);
77
constappKey=(appId:string)=>appsKey.concat(appId);
88
constappSecretsKey=(appId:string)=>appKey(appId).concat("secrets");
99

10+
exportconstgetGitHubDevice=()=>{
11+
return{
12+
queryKey:["oauth2-provider","github","device"],
13+
queryFn:()=>API.getOAuth2GitHubDevice(),
14+
};
15+
};
16+
17+
exportconstgetGitHubDeviceFlowCallback=(code:string,state:string)=>{
18+
return{
19+
queryKey:["oauth2-provider","github","callback",code,state],
20+
queryFn:()=>API.getOAuth2GitHubDeviceFlowCallback(code,state),
21+
};
22+
};
23+
1024
exportconstgetApps=(userId?:string)=>{
1125
return{
1226
queryKey:userId ?appsKey.concat(userId) :appsKey,
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
importtype{Interpolation,Theme}from"@emotion/react";
2+
importOpenInNewIconfrom"@mui/icons-material/OpenInNew";
3+
importAlertTitlefrom"@mui/material/AlertTitle";
4+
importCircularProgressfrom"@mui/material/CircularProgress";
5+
importLinkfrom"@mui/material/Link";
6+
importtype{ApiErrorResponse}from"api/errors";
7+
importtype{ExternalAuthDevice}from"api/typesGenerated";
8+
import{Alert,AlertDetail}from"components/Alert/Alert";
9+
import{CopyButton}from"components/CopyButton/CopyButton";
10+
importtype{FC}from"react";
11+
12+
interfaceGitDeviceAuthProps{
13+
externalAuthDevice?:ExternalAuthDevice;
14+
deviceExchangeError?:ApiErrorResponse;
15+
}
16+
17+
exportconstGitDeviceAuth:FC<GitDeviceAuthProps>=({
18+
externalAuthDevice,
19+
deviceExchangeError,
20+
})=>{
21+
letstatus=(
22+
<pcss={styles.status}>
23+
<CircularProgresssize={16}color="secondary"data-chromatic="ignore"/>
24+
Checking for authentication...
25+
</p>
26+
);
27+
if(deviceExchangeError){
28+
// See https://datatracker.ietf.org/doc/html/rfc8628#section-3.5
29+
switch(deviceExchangeError.detail){
30+
case"authorization_pending":
31+
break;
32+
case"expired_token":
33+
status=(
34+
<Alertseverity="error">
35+
The one-time code has expired. Refresh to get a new one!
36+
</Alert>
37+
);
38+
break;
39+
case"access_denied":
40+
status=(
41+
<Alertseverity="error">Access to the Git provider was denied.</Alert>
42+
);
43+
break;
44+
default:
45+
status=(
46+
<Alertseverity="error">
47+
<AlertTitle>{deviceExchangeError.message}</AlertTitle>
48+
{deviceExchangeError.detail&&(
49+
<AlertDetail>{deviceExchangeError.detail}</AlertDetail>
50+
)}
51+
</Alert>
52+
);
53+
break;
54+
}
55+
}
56+
57+
// If the error comes from the `externalAuthDevice` query,
58+
// we cannot even display the user_code.
59+
if(deviceExchangeError&&!externalAuthDevice){
60+
return<div>{status}</div>;
61+
}
62+
63+
if(!externalAuthDevice){
64+
return<CircularProgress/>;
65+
}
66+
67+
return(
68+
<div>
69+
<pcss={styles.text}>
70+
Copy your one-time code:&nbsp;
71+
<divcss={styles.copyCode}>
72+
<spancss={styles.code}>{externalAuthDevice.user_code}</span>
73+
&nbsp;<CopyButtontext={externalAuthDevice.user_code}/>
74+
</div>
75+
<br/>
76+
Then open the link below and paste it:
77+
</p>
78+
<divcss={styles.links}>
79+
<Link
80+
css={styles.link}
81+
href={externalAuthDevice.verification_uri}
82+
target="_blank"
83+
rel="noreferrer"
84+
>
85+
<OpenInNewIconfontSize="small"/>
86+
Open and Paste
87+
</Link>
88+
</div>
89+
90+
{status}
91+
</div>
92+
);
93+
};
94+
95+
conststyles={
96+
text:(theme)=>({
97+
fontSize:16,
98+
color:theme.palette.text.secondary,
99+
textAlign:"center",
100+
lineHeight:"160%",
101+
margin:0,
102+
}),
103+
104+
copyCode:{
105+
display:"inline-flex",
106+
alignItems:"center",
107+
},
108+
109+
code:(theme)=>({
110+
fontWeight:"bold",
111+
color:theme.palette.text.primary,
112+
}),
113+
114+
links:{
115+
display:"flex",
116+
gap:4,
117+
margin:16,
118+
flexDirection:"column",
119+
},
120+
121+
link:{
122+
display:"flex",
123+
alignItems:"center",
124+
justifyContent:"center",
125+
fontSize:16,
126+
gap:8,
127+
},
128+
129+
status:(theme)=>({
130+
display:"flex",
131+
alignItems:"center",
132+
justifyContent:"center",
133+
gap:8,
134+
color:theme.palette.text.disabled,
135+
}),
136+
}satisfiesRecord<string,Interpolation<Theme>>;

‎site/src/pages/ExternalAuthPage/ExternalAuthPageView.tsx

Lines changed: 2 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
importtype{Interpolation,Theme}from"@emotion/react";
22
importOpenInNewIconfrom"@mui/icons-material/OpenInNew";
33
importRefreshIconfrom"@mui/icons-material/Refresh";
4-
importAlertTitlefrom"@mui/material/AlertTitle";
5-
importCircularProgressfrom"@mui/material/CircularProgress";
64
importLinkfrom"@mui/material/Link";
75
importTooltipfrom"@mui/material/Tooltip";
86
importtype{ApiErrorResponse}from"api/errors";
97
importtype{ExternalAuth,ExternalAuthDevice}from"api/typesGenerated";
10-
import{Alert,AlertDetail}from"components/Alert/Alert";
8+
import{Alert}from"components/Alert/Alert";
119
import{Avatar}from"components/Avatar/Avatar";
12-
import{CopyButton}from"components/CopyButton/CopyButton";
10+
import{GitDeviceAuth}from"components/GitDeviceAuth/GitDeviceAuth";
1311
import{SignInLayout}from"components/SignInLayout/SignInLayout";
1412
import{Welcome}from"components/Welcome/Welcome";
1513
importtype{FC,ReactNode}from"react";
@@ -141,89 +139,6 @@ const ExternalAuthPageView: FC<ExternalAuthPageViewProps> = ({
141139
);
142140
};
143141

144-
interfaceGitDeviceAuthProps{
145-
externalAuthDevice?:ExternalAuthDevice;
146-
deviceExchangeError?:ApiErrorResponse;
147-
}
148-
149-
constGitDeviceAuth:FC<GitDeviceAuthProps>=({
150-
externalAuthDevice,
151-
deviceExchangeError,
152-
})=>{
153-
letstatus=(
154-
<pcss={styles.status}>
155-
<CircularProgresssize={16}color="secondary"data-chromatic="ignore"/>
156-
Checking for authentication...
157-
</p>
158-
);
159-
if(deviceExchangeError){
160-
// See https://datatracker.ietf.org/doc/html/rfc8628#section-3.5
161-
switch(deviceExchangeError.detail){
162-
case"authorization_pending":
163-
break;
164-
case"expired_token":
165-
status=(
166-
<Alertseverity="error">
167-
The one-time code has expired. Refresh to get a new one!
168-
</Alert>
169-
);
170-
break;
171-
case"access_denied":
172-
status=(
173-
<Alertseverity="error">Access to the Git provider was denied.</Alert>
174-
);
175-
break;
176-
default:
177-
status=(
178-
<Alertseverity="error">
179-
<AlertTitle>{deviceExchangeError.message}</AlertTitle>
180-
{deviceExchangeError.detail&&(
181-
<AlertDetail>{deviceExchangeError.detail}</AlertDetail>
182-
)}
183-
</Alert>
184-
);
185-
break;
186-
}
187-
}
188-
189-
// If the error comes from the `externalAuthDevice` query,
190-
// we cannot even display the user_code.
191-
if(deviceExchangeError&&!externalAuthDevice){
192-
return<div>{status}</div>;
193-
}
194-
195-
if(!externalAuthDevice){
196-
return<CircularProgress/>;
197-
}
198-
199-
return(
200-
<div>
201-
<pcss={styles.text}>
202-
Copy your one-time code:&nbsp;
203-
<divcss={styles.copyCode}>
204-
<spancss={styles.code}>{externalAuthDevice.user_code}</span>
205-
&nbsp;<CopyButtontext={externalAuthDevice.user_code}/>
206-
</div>
207-
<br/>
208-
Then open the link below and paste it:
209-
</p>
210-
<divcss={styles.links}>
211-
<Link
212-
css={styles.link}
213-
href={externalAuthDevice.verification_uri}
214-
target="_blank"
215-
rel="noreferrer"
216-
>
217-
<OpenInNewIconfontSize="small"/>
218-
Open and Paste
219-
</Link>
220-
</div>
221-
222-
{status}
223-
</div>
224-
);
225-
};
226-
227142
exportdefaultExternalAuthPageView;
228143

229144
conststyles={
@@ -235,16 +150,6 @@ const styles = {
235150
margin:0,
236151
}),
237152

238-
copyCode:{
239-
display:"inline-flex",
240-
alignItems:"center",
241-
},
242-
243-
code:(theme)=>({
244-
fontWeight:"bold",
245-
color:theme.palette.text.primary,
246-
}),
247-
248153
installAlert:{
249154
margin:16,
250155
},
@@ -264,14 +169,6 @@ const styles = {
264169
gap:8,
265170
},
266171

267-
status:(theme)=>({
268-
display:"flex",
269-
alignItems:"center",
270-
justifyContent:"center",
271-
gap:8,
272-
color:theme.palette.text.disabled,
273-
}),
274-
275172
authorizedInstalls:(theme)=>({
276173
display:"flex",
277174
gap:4,

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp