Skip to content

Commit 7cfb64e

Browse files
authored
fix: login (#161)
1 parent ec3a8ff commit 7cfb64e

File tree

89 files changed

+1031
-1166
lines changed

Some content is hidden

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

89 files changed

+1031
-1166
lines changed

.github/workflows/build-check.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ name: Build and check the code format
33
on:
44
push:
55
branches:
6-
- main
6+
- '*'
77
pull_request_target:
88
types:
99
- opened

.vscode/launch.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"name": "Run Extension",
1010
"type": "extensionHost",
1111
"request": "launch",
12-
"args": ["--extensionDevelopmentPath=${workspaceFolder}", "--disable-extensions"],
12+
"args": ["--extensionDevelopmentPath=${workspaceFolder}"],
1313
"outFiles": ["${workspaceFolder}/dist/**/*.js"],
1414
"preLaunchTask": "${defaultBuildTask}",
1515
"env": {

__mocks__/vscode.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,12 +110,12 @@ const QuickPickItemKind = {
110110
const TreeItem = jest.fn()
111111
const Disposable = jest.fn()
112112
;(Disposable as any).from = jest.fn()
113-
const authentication = { registerAuthenticationProvider: jest.fn() }
113+
const auth = { registerAuthenticationProvider: jest.fn() }
114114

115115
export = {
116116
ThemeColor,
117117
Disposable,
118-
authentication,
118+
auth,
119119
CodeLens,
120120
languages,
121121
StatusBarAlignment,

src/auth/access-token.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export type AccessToken = {
2+
exp: number
3+
}

src/auth/account-info.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { UserInfoSpec } from '@/services/oauth.api'
2+
import { trim } from 'lodash-es'
3+
import { AuthenticationSessionAccountInformation } from 'vscode'
4+
import { AuthProvider } from './auth-provider'
5+
6+
export class AccountInfo implements AuthenticationSessionAccountInformation {
7+
readonly label: string
8+
readonly id: string
9+
10+
private _blogApp: string | null = null
11+
12+
private constructor(
13+
public readonly name: string,
14+
public readonly avatar: string,
15+
public readonly website: string, //The user blog home page url
16+
public readonly blogId: number,
17+
public readonly sub: string, //UserId(data type is Guid)
18+
public readonly accountId: number //SpaceUserId
19+
) {
20+
this.id = `${this.accountId}-${AuthProvider.providerId}`
21+
this.label = name
22+
}
23+
24+
get userId() {
25+
return this.sub
26+
}
27+
28+
get blogApp(): string | null {
29+
this._blogApp ??= this.parseBlogApp()
30+
return this._blogApp
31+
}
32+
33+
static newAnonymous = () => new AccountInfo('anonymous', '', '', -1, '', -1)
34+
35+
static from = (userInfo: UserInfoSpec) =>
36+
new AccountInfo(
37+
userInfo.name,
38+
userInfo.picture,
39+
userInfo.website,
40+
parseInt(userInfo.blog_id, 10),
41+
userInfo.sub,
42+
parseInt(userInfo.account_id, 10)
43+
)
44+
45+
private parseBlogApp = () =>
46+
trim(this.website ?? '', '/')
47+
.split('/')
48+
.pop() ?? null
49+
}

src/auth/account-manager.ts

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { AccountInfo } from './account-info'
2+
import { globalCtx } from '@/services/global-ctx'
3+
import vscode, { authentication, AuthenticationGetSessionOptions, Disposable } from 'vscode'
4+
import { accountViewDataProvider } from '@/tree-view-providers/account-view-data-provider'
5+
import { postsDataProvider } from '@/tree-view-providers/posts-data-provider'
6+
import { postCategoriesDataProvider } from '@/tree-view-providers/post-categories-tree-data-provider'
7+
import { Oauth } from '@/services/oauth.api'
8+
import { AuthProvider } from '@/auth/auth-provider'
9+
import { AuthSession } from '@/auth/auth-session'
10+
import { BlogExportProvider } from '@/tree-view-providers/blog-export-provider'
11+
import { AlertService } from '@/services/alert.service'
12+
13+
const isAuthorizedStorageKey = 'isAuthorized'
14+
15+
export const ACQUIRE_TOKEN_REJECT_UNAUTHENTICATED = 'unauthenticated'
16+
export const ACQUIRE_TOKEN_REJECT_EXPIRED = 'expired'
17+
18+
class AccountManager extends vscode.Disposable {
19+
private readonly _disposable = Disposable.from(
20+
AuthProvider.instance.onDidChangeSessions(async ({ added }) => {
21+
this._session = null
22+
if (added != null && added.length > 0) await this.ensureSession()
23+
24+
await this.updateAuthStatus()
25+
26+
accountViewDataProvider.fireTreeDataChangedEvent()
27+
postsDataProvider.fireTreeDataChangedEvent(undefined)
28+
postCategoriesDataProvider.fireTreeDataChangedEvent()
29+
30+
BlogExportProvider.optionalInstance?.refreshRecords({ force: false, clearCache: true }).catch(console.warn)
31+
})
32+
)
33+
34+
private _session: AuthSession | null = null
35+
36+
constructor() {
37+
super(() => {
38+
this._disposable.dispose()
39+
})
40+
}
41+
42+
get isAuthorized() {
43+
return this._session !== null
44+
}
45+
46+
get currentUser(): AccountInfo {
47+
return this._session?.account ?? AccountInfo.newAnonymous()
48+
}
49+
50+
/**
51+
* Acquire the access token.
52+
* This will reject with a human-readable reason string if not sign-in or the token has expired.
53+
* @returns The access token of the active session
54+
*/
55+
async acquireToken(): Promise<string> {
56+
const session = await this.ensureSession({ createIfNone: false })
57+
58+
if (session == null) return Promise.reject(ACQUIRE_TOKEN_REJECT_UNAUTHENTICATED)
59+
60+
if (session.isExpired) return Promise.reject(ACQUIRE_TOKEN_REJECT_EXPIRED)
61+
62+
return session.accessToken
63+
}
64+
65+
async login() {
66+
await this.ensureSession({ createIfNone: false, forceNewSession: true })
67+
}
68+
69+
async logout() {
70+
if (!this.isAuthorized) return
71+
72+
const session = await authentication.getSession(AuthProvider.providerId, [])
73+
74+
// WRN: For old version compatibility, **never** remove this line
75+
await globalCtx.storage.update('user', undefined)
76+
77+
if (session === undefined) return
78+
79+
try {
80+
await Oauth.revokeToken(session.accessToken)
81+
await AuthProvider.instance.removeSession(session.id)
82+
} catch (e: any) {
83+
void AlertService.err(`登出发生错误: ${e}`)
84+
}
85+
}
86+
87+
async updateAuthStatus() {
88+
await this.ensureSession({ createIfNone: false })
89+
90+
await vscode.commands.executeCommand(
91+
'setContext',
92+
`${globalCtx.extName}.${isAuthorizedStorageKey}`,
93+
this.isAuthorized
94+
)
95+
96+
if (this.isAuthorized) {
97+
await vscode.commands.executeCommand('setContext', `${globalCtx.extName}.user`, {
98+
name: this.currentUser.name,
99+
avatar: this.currentUser.avatar,
100+
})
101+
}
102+
}
103+
104+
private async ensureSession(opt?: AuthenticationGetSessionOptions): Promise<AuthSession | null> {
105+
const session = await authentication.getSession(AuthProvider.instance.providerId, [], opt).then(
106+
session => (session ? AuthSession.from(session) : null),
107+
e => {
108+
AlertService.err(`创建/获取 Session 失败: ${e}`)
109+
}
110+
)
111+
112+
if (session != null && session.account.accountId < 0) {
113+
this._session = null
114+
await AuthProvider.instance.removeSession(session.id)
115+
} else {
116+
this._session = session
117+
}
118+
119+
return this._session
120+
}
121+
}
122+
123+
export const accountManager = new AccountManager()

0 commit comments

Comments
 (0)