@alien_org/sso-sdk-core 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/client.d.ts +30 -0
- package/dist/client.js +200 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.js +13 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/schema.d.ts +80 -0
- package/dist/schema.js +68 -0
- package/dist/server.d.ts +14 -0
- package/dist/server.js +60 -0
- package/package.json +53 -0
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { AuthorizeResponse, TokenInfo, UserInfo } from './schema';
|
|
2
|
+
import { z } from 'zod/v4-mini';
|
|
3
|
+
export interface JWTHeader {
|
|
4
|
+
alg: string;
|
|
5
|
+
typ: string;
|
|
6
|
+
}
|
|
7
|
+
export declare const AlienSsoSdkClientSchema: z.ZodMiniObject<{
|
|
8
|
+
serverSdkBaseUrl: z.ZodMiniString<string>;
|
|
9
|
+
ssoBaseUrl: z.ZodMiniURL;
|
|
10
|
+
pollingInterval: z.ZodMiniOptional<z.ZodMiniNumber<number>>;
|
|
11
|
+
}, z.core.$strip>;
|
|
12
|
+
export type AlienSsoSdkClientConfig = z.infer<typeof AlienSsoSdkClientSchema>;
|
|
13
|
+
export declare class AlienSsoSdkClient {
|
|
14
|
+
readonly config: AlienSsoSdkClientConfig;
|
|
15
|
+
readonly pollingInterval: number;
|
|
16
|
+
readonly serverSdkBaseUrl: string;
|
|
17
|
+
readonly ssoBaseUrl: string;
|
|
18
|
+
constructor(config: AlienSsoSdkClientConfig);
|
|
19
|
+
private generateCodeVerifier;
|
|
20
|
+
private generateCodeChallenge;
|
|
21
|
+
getAuthDeeplink(): Promise<AuthorizeResponse>;
|
|
22
|
+
pollAuth(pollingCode: string): Promise<string | null>;
|
|
23
|
+
exchangeToken(authorizationCode: string): Promise<string | null>;
|
|
24
|
+
verifyAuth(): Promise<boolean>;
|
|
25
|
+
getAccessToken(): string | null;
|
|
26
|
+
getUserInfo(): (TokenInfo & {
|
|
27
|
+
user: UserInfo;
|
|
28
|
+
}) | null;
|
|
29
|
+
logout(): void;
|
|
30
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { AuthorizeResponseSchema, ExchangeCodeRequestSchema, ExchangeCodeResponseSchema, InternalAuthorizeRequestSchema, PollRequestSchema, PollResponseSchema, TokenInfoSchema, UserInfoSchema, VerifyTokenRequestSchema, VerifyTokenResponseSchema, } from './schema';
|
|
2
|
+
import { z } from 'zod/v4-mini';
|
|
3
|
+
import base64url from 'base64url';
|
|
4
|
+
import CryptoJS from 'crypto-js';
|
|
5
|
+
const SERVER_SDK_BASEURL = 'http://localhost:3000';
|
|
6
|
+
const SSO_BASE_URL = 'https://sso.alien.com';
|
|
7
|
+
const POLLING_INTERVAL = 5000;
|
|
8
|
+
const STORAGE_KEY = 'alien-sso_';
|
|
9
|
+
export const AlienSsoSdkClientSchema = z.object({
|
|
10
|
+
serverSdkBaseUrl: z.string(),
|
|
11
|
+
ssoBaseUrl: z.url(),
|
|
12
|
+
pollingInterval: z.optional(z.number()),
|
|
13
|
+
});
|
|
14
|
+
export class AlienSsoSdkClient {
|
|
15
|
+
constructor(config) {
|
|
16
|
+
this.config = AlienSsoSdkClientSchema.parse(config);
|
|
17
|
+
this.ssoBaseUrl = this.config.ssoBaseUrl || SSO_BASE_URL;
|
|
18
|
+
this.serverSdkBaseUrl = this.config.serverSdkBaseUrl || SERVER_SDK_BASEURL;
|
|
19
|
+
this.pollingInterval = this.config.pollingInterval || POLLING_INTERVAL;
|
|
20
|
+
}
|
|
21
|
+
generateCodeVerifier(length = 128) {
|
|
22
|
+
let array;
|
|
23
|
+
const cryptoObj = typeof window !== 'undefined' && window.crypto;
|
|
24
|
+
if (cryptoObj && cryptoObj.getRandomValues) {
|
|
25
|
+
array = new Uint8Array(length);
|
|
26
|
+
cryptoObj.getRandomValues(array);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
array = new Uint8Array(length);
|
|
30
|
+
for (let i = 0; i < length; i++) {
|
|
31
|
+
array[i] = Math.floor(Math.random() * 256);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
let str = '';
|
|
35
|
+
for (let i = 0; i < array.length; i++) {
|
|
36
|
+
str += String.fromCharCode(array[i]);
|
|
37
|
+
}
|
|
38
|
+
return base64url.encode(str);
|
|
39
|
+
}
|
|
40
|
+
async generateCodeChallenge(codeVerifier) {
|
|
41
|
+
return CryptoJS.SHA256(codeVerifier).toString(CryptoJS.enc.Hex);
|
|
42
|
+
}
|
|
43
|
+
async getAuthDeeplink() {
|
|
44
|
+
const codeVerifier = this.generateCodeVerifier();
|
|
45
|
+
const codeChallenge = await this.generateCodeChallenge(codeVerifier);
|
|
46
|
+
sessionStorage.setItem(STORAGE_KEY + 'code_verifier', codeVerifier);
|
|
47
|
+
const authorizeUrl = `${this.config.serverSdkBaseUrl}/authorize`;
|
|
48
|
+
const authorizePayload = {
|
|
49
|
+
code_challenge: codeChallenge,
|
|
50
|
+
};
|
|
51
|
+
InternalAuthorizeRequestSchema.parse(authorizePayload);
|
|
52
|
+
const response = await fetch(authorizeUrl, {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
headers: {
|
|
55
|
+
'Content-Type': 'application/json',
|
|
56
|
+
},
|
|
57
|
+
body: JSON.stringify(authorizePayload),
|
|
58
|
+
});
|
|
59
|
+
const json = await response.json();
|
|
60
|
+
return AuthorizeResponseSchema.parse(json);
|
|
61
|
+
}
|
|
62
|
+
async pollAuth(pollingCode) {
|
|
63
|
+
const pollPayload = {
|
|
64
|
+
polling_code: pollingCode,
|
|
65
|
+
};
|
|
66
|
+
PollRequestSchema.parse(pollPayload);
|
|
67
|
+
const pollingUrl = `${this.config.ssoBaseUrl}/poll`;
|
|
68
|
+
while (true) {
|
|
69
|
+
const response = await fetch(pollingUrl, {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers: {
|
|
72
|
+
'Content-Type': 'application/json',
|
|
73
|
+
},
|
|
74
|
+
body: JSON.stringify(pollPayload),
|
|
75
|
+
});
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
throw new Error(`Poll failed: ${response.statusText}`);
|
|
78
|
+
}
|
|
79
|
+
const json = await response.json();
|
|
80
|
+
const pollResponse = PollResponseSchema.parse(json);
|
|
81
|
+
if (pollResponse.status === 'authorized' &&
|
|
82
|
+
pollResponse.authorization_code) {
|
|
83
|
+
return pollResponse.authorization_code;
|
|
84
|
+
}
|
|
85
|
+
if (pollResponse.status === 'pending') {
|
|
86
|
+
await new Promise((resolve) => setTimeout(resolve, this.pollingInterval));
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
throw new Error(`Poll failed`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
async exchangeToken(authorizationCode) {
|
|
94
|
+
const codeVerifier = sessionStorage.getItem(STORAGE_KEY + 'code_verifier');
|
|
95
|
+
if (!codeVerifier)
|
|
96
|
+
throw new Error('Missing code verifier.');
|
|
97
|
+
const exchangeCodePayload = {
|
|
98
|
+
authorization_code: authorizationCode,
|
|
99
|
+
code_verifier: codeVerifier,
|
|
100
|
+
};
|
|
101
|
+
ExchangeCodeRequestSchema.parse(exchangeCodePayload);
|
|
102
|
+
const exchangeUrl = `${this.config.ssoBaseUrl}/access_token/exchange`;
|
|
103
|
+
const response = await fetch(exchangeUrl, {
|
|
104
|
+
method: 'POST',
|
|
105
|
+
headers: {
|
|
106
|
+
'Content-Type': 'application/json',
|
|
107
|
+
},
|
|
108
|
+
body: JSON.stringify(exchangeCodePayload),
|
|
109
|
+
});
|
|
110
|
+
if (!response.ok) {
|
|
111
|
+
throw new Error(`ExchangeCode failed: ${response.statusText}`);
|
|
112
|
+
}
|
|
113
|
+
const json = await response.json();
|
|
114
|
+
const exchangeCodeResponse = ExchangeCodeResponseSchema.parse(json);
|
|
115
|
+
if (exchangeCodeResponse.access_token) {
|
|
116
|
+
localStorage.setItem(STORAGE_KEY + 'access_token', exchangeCodeResponse.access_token);
|
|
117
|
+
return exchangeCodeResponse.access_token;
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
throw new Error('Exchange failed');
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async verifyAuth() {
|
|
124
|
+
const access_token = this.getAccessToken();
|
|
125
|
+
if (!access_token) {
|
|
126
|
+
throw new Error('Access token is invalid.');
|
|
127
|
+
}
|
|
128
|
+
const verifyTokenPayload = {
|
|
129
|
+
access_token,
|
|
130
|
+
};
|
|
131
|
+
VerifyTokenRequestSchema.parse(verifyTokenPayload);
|
|
132
|
+
const verifyTokenUrl = `${this.config.ssoBaseUrl}/access_token/verify`;
|
|
133
|
+
const response = await fetch(verifyTokenUrl, {
|
|
134
|
+
method: 'POST',
|
|
135
|
+
headers: {
|
|
136
|
+
'Content-Type': 'application/json',
|
|
137
|
+
},
|
|
138
|
+
body: JSON.stringify(verifyTokenPayload),
|
|
139
|
+
});
|
|
140
|
+
if (!response.ok) {
|
|
141
|
+
throw new Error(`VerifyToken failed: ${response.statusText}`);
|
|
142
|
+
}
|
|
143
|
+
const json = await response.json();
|
|
144
|
+
const verifyTokenResponse = VerifyTokenResponseSchema.parse(json);
|
|
145
|
+
if (!verifyTokenResponse.is_valid) {
|
|
146
|
+
throw new Error('Access token is invalid.');
|
|
147
|
+
}
|
|
148
|
+
return verifyTokenResponse.is_valid;
|
|
149
|
+
}
|
|
150
|
+
getAccessToken() {
|
|
151
|
+
return localStorage.getItem(STORAGE_KEY + 'access_token');
|
|
152
|
+
}
|
|
153
|
+
getUserInfo() {
|
|
154
|
+
const token = this.getAccessToken();
|
|
155
|
+
if (!token)
|
|
156
|
+
return null;
|
|
157
|
+
const tokenParts = token.split('.');
|
|
158
|
+
if (tokenParts.length !== 3) {
|
|
159
|
+
throw new Error('Invalid token format');
|
|
160
|
+
}
|
|
161
|
+
const headerPart = tokenParts[0];
|
|
162
|
+
if (!headerPart)
|
|
163
|
+
return null;
|
|
164
|
+
let header;
|
|
165
|
+
try {
|
|
166
|
+
const headerJson = base64url.decode(headerPart);
|
|
167
|
+
header = JSON.parse(headerJson);
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
throw new Error('Invalid token header format');
|
|
171
|
+
}
|
|
172
|
+
if (header.alg !== 'HS256' || header.typ !== 'JWT') {
|
|
173
|
+
throw new Error('Unsupported token algorithm or type');
|
|
174
|
+
}
|
|
175
|
+
const payloadPart = tokenParts[1];
|
|
176
|
+
if (!payloadPart)
|
|
177
|
+
return null;
|
|
178
|
+
let payload;
|
|
179
|
+
try {
|
|
180
|
+
const payloadJson = JSON.parse(base64url.decode(payloadPart));
|
|
181
|
+
payload = TokenInfoSchema.parse(payloadJson);
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
throw new Error('Invalid token payload format');
|
|
185
|
+
}
|
|
186
|
+
let user;
|
|
187
|
+
try {
|
|
188
|
+
const userJson = JSON.parse(payload.app_callback_payload);
|
|
189
|
+
user = UserInfoSchema.parse(userJson);
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
throw new Error('Invalid app_callback_payload JSON format');
|
|
193
|
+
}
|
|
194
|
+
return Object.assign({}, payload, { user });
|
|
195
|
+
}
|
|
196
|
+
logout() {
|
|
197
|
+
localStorage.removeItem(STORAGE_KEY + 'access_token');
|
|
198
|
+
sessionStorage.removeItem(STORAGE_KEY + 'code_verifier');
|
|
199
|
+
}
|
|
200
|
+
}
|
package/dist/errors.d.ts
ADDED
package/dist/errors.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class BaseError extends Error {
|
|
2
|
+
constructor(message) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = this.constructor.name;
|
|
5
|
+
if (Error.captureStackTrace) {
|
|
6
|
+
Error.captureStackTrace(this, this.constructor);
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export class ValidationError extends BaseError {
|
|
11
|
+
}
|
|
12
|
+
export class AuthenticationError extends BaseError {
|
|
13
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { z } from 'zod/v4-mini';
|
|
2
|
+
/**
|
|
3
|
+
* Internal Authorize request/response schema to server SDK
|
|
4
|
+
*/
|
|
5
|
+
export declare const InternalAuthorizeRequestSchema: z.ZodMiniObject<{
|
|
6
|
+
code_challenge: z.ZodMiniString<string>;
|
|
7
|
+
}, z.core.$strip>;
|
|
8
|
+
export type InternalAuthorizeRequest = z.infer<typeof InternalAuthorizeRequestSchema>;
|
|
9
|
+
/**
|
|
10
|
+
* Authorize request/response schema
|
|
11
|
+
*/
|
|
12
|
+
export declare const AuthorizeRequestSchema: z.ZodMiniObject<{
|
|
13
|
+
code_challenge: z.ZodMiniString<string>;
|
|
14
|
+
code_challenge_method: z.ZodMiniLiteral<"S256">;
|
|
15
|
+
provider_address: z.ZodMiniString<string>;
|
|
16
|
+
provider_signature: z.ZodMiniString<string>;
|
|
17
|
+
}, z.core.$strip>;
|
|
18
|
+
export type AuthorizeRequest = z.infer<typeof AuthorizeRequestSchema>;
|
|
19
|
+
export declare const AuthorizeResponseSchema: z.ZodMiniObject<{
|
|
20
|
+
deep_link: z.ZodMiniString<string>;
|
|
21
|
+
polling_code: z.ZodMiniString<string>;
|
|
22
|
+
expired_at: z.ZodMiniNumber<number>;
|
|
23
|
+
}, z.core.$strip>;
|
|
24
|
+
export type AuthorizeResponse = z.infer<typeof AuthorizeResponseSchema>;
|
|
25
|
+
/**
|
|
26
|
+
* Poll request/response schema
|
|
27
|
+
*/
|
|
28
|
+
export declare const PollRequestSchema: z.ZodMiniObject<{
|
|
29
|
+
polling_code: z.ZodMiniString<string>;
|
|
30
|
+
}, z.core.$strip>;
|
|
31
|
+
export type PollRequest = z.infer<typeof PollRequestSchema>;
|
|
32
|
+
export declare const PollResponseSchema: z.ZodMiniObject<{
|
|
33
|
+
status: z.ZodMiniEnum<{
|
|
34
|
+
pending: "pending";
|
|
35
|
+
authorized: "authorized";
|
|
36
|
+
}>;
|
|
37
|
+
authorization_code: z.ZodMiniOptional<z.ZodMiniString<string>>;
|
|
38
|
+
}, z.core.$strip>;
|
|
39
|
+
export type PollResponse = z.infer<typeof PollResponseSchema>;
|
|
40
|
+
/**
|
|
41
|
+
* ExchangeCode request/response schema
|
|
42
|
+
*/
|
|
43
|
+
export declare const ExchangeCodeRequestSchema: z.ZodMiniObject<{
|
|
44
|
+
authorization_code: z.ZodMiniString<string>;
|
|
45
|
+
code_verifier: z.ZodMiniString<string>;
|
|
46
|
+
}, z.core.$strip>;
|
|
47
|
+
export type ExchangeCodeRequest = z.infer<typeof ExchangeCodeRequestSchema>;
|
|
48
|
+
export declare const ExchangeCodeResponseSchema: z.ZodMiniObject<{
|
|
49
|
+
access_token: z.ZodMiniString<string>;
|
|
50
|
+
}, z.core.$strip>;
|
|
51
|
+
export type ExchangeCodeResponse = z.infer<typeof ExchangeCodeResponseSchema>;
|
|
52
|
+
/**
|
|
53
|
+
* VerifyToken request/response schema
|
|
54
|
+
*/
|
|
55
|
+
export declare const VerifyTokenRequestSchema: z.ZodMiniObject<{
|
|
56
|
+
access_token: z.ZodMiniString<string>;
|
|
57
|
+
}, z.core.$strip>;
|
|
58
|
+
export type VerifyTokenRequest = z.infer<typeof VerifyTokenRequestSchema>;
|
|
59
|
+
export declare const VerifyTokenResponseSchema: z.ZodMiniObject<{
|
|
60
|
+
is_valid: z.ZodMiniBoolean<boolean>;
|
|
61
|
+
}, z.core.$strip>;
|
|
62
|
+
export type VerifyTokenResponse = z.infer<typeof VerifyTokenResponseSchema>;
|
|
63
|
+
/**
|
|
64
|
+
* User info schema
|
|
65
|
+
*/
|
|
66
|
+
export declare const UserInfoSchema: z.ZodMiniObject<{
|
|
67
|
+
session_address: z.ZodMiniString<string>;
|
|
68
|
+
}, z.core.$strip>;
|
|
69
|
+
export type UserInfo = z.infer<typeof UserInfoSchema>;
|
|
70
|
+
/**
|
|
71
|
+
* Token info schema
|
|
72
|
+
*/
|
|
73
|
+
export declare const TokenInfoSchema: z.ZodMiniObject<{
|
|
74
|
+
app_callback_payload: z.ZodMiniString<string>;
|
|
75
|
+
app_callback_session_signature: z.ZodMiniString<string>;
|
|
76
|
+
app_callback_session_address: z.ZodMiniString<string>;
|
|
77
|
+
expired_at: z.ZodMiniNumber<number>;
|
|
78
|
+
issued_at: z.ZodMiniNumber<number>;
|
|
79
|
+
}, z.core.$strip>;
|
|
80
|
+
export type TokenInfo = z.infer<typeof TokenInfoSchema>;
|
package/dist/schema.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { z } from 'zod/v4-mini';
|
|
2
|
+
/**
|
|
3
|
+
* Internal Authorize request/response schema to server SDK
|
|
4
|
+
*/
|
|
5
|
+
export const InternalAuthorizeRequestSchema = z.object({
|
|
6
|
+
code_challenge: z.string(),
|
|
7
|
+
});
|
|
8
|
+
/**
|
|
9
|
+
* Authorize request/response schema
|
|
10
|
+
*/
|
|
11
|
+
export const AuthorizeRequestSchema = z.object({
|
|
12
|
+
code_challenge: z.string(),
|
|
13
|
+
code_challenge_method: z.literal('S256'),
|
|
14
|
+
provider_address: z.string(),
|
|
15
|
+
provider_signature: z.string(),
|
|
16
|
+
});
|
|
17
|
+
export const AuthorizeResponseSchema = z.object({
|
|
18
|
+
deep_link: z.string(),
|
|
19
|
+
polling_code: z.string(),
|
|
20
|
+
expired_at: z.number(),
|
|
21
|
+
});
|
|
22
|
+
/**
|
|
23
|
+
* Poll request/response schema
|
|
24
|
+
*/
|
|
25
|
+
export const PollRequestSchema = z.object({
|
|
26
|
+
polling_code: z.string(),
|
|
27
|
+
});
|
|
28
|
+
const status = ['pending', 'authorized'];
|
|
29
|
+
const StatusEnum = z.enum(status);
|
|
30
|
+
export const PollResponseSchema = z.object({
|
|
31
|
+
status: StatusEnum,
|
|
32
|
+
authorization_code: z.optional(z.string()),
|
|
33
|
+
});
|
|
34
|
+
/**
|
|
35
|
+
* ExchangeCode request/response schema
|
|
36
|
+
*/
|
|
37
|
+
export const ExchangeCodeRequestSchema = z.object({
|
|
38
|
+
authorization_code: z.string(),
|
|
39
|
+
code_verifier: z.string(),
|
|
40
|
+
});
|
|
41
|
+
export const ExchangeCodeResponseSchema = z.object({
|
|
42
|
+
access_token: z.string(),
|
|
43
|
+
});
|
|
44
|
+
/**
|
|
45
|
+
* VerifyToken request/response schema
|
|
46
|
+
*/
|
|
47
|
+
export const VerifyTokenRequestSchema = z.object({
|
|
48
|
+
access_token: z.string(),
|
|
49
|
+
});
|
|
50
|
+
export const VerifyTokenResponseSchema = z.object({
|
|
51
|
+
is_valid: z.boolean(),
|
|
52
|
+
});
|
|
53
|
+
/**
|
|
54
|
+
* User info schema
|
|
55
|
+
*/
|
|
56
|
+
export const UserInfoSchema = z.object({
|
|
57
|
+
session_address: z.string(),
|
|
58
|
+
});
|
|
59
|
+
/**
|
|
60
|
+
* Token info schema
|
|
61
|
+
*/
|
|
62
|
+
export const TokenInfoSchema = z.object({
|
|
63
|
+
app_callback_payload: z.string(),
|
|
64
|
+
app_callback_session_signature: z.string(),
|
|
65
|
+
app_callback_session_address: z.string(),
|
|
66
|
+
expired_at: z.number(),
|
|
67
|
+
issued_at: z.number(),
|
|
68
|
+
});
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { AuthorizeResponse } from './schema';
|
|
2
|
+
import { z } from 'zod/v4-mini';
|
|
3
|
+
export declare const AlienSsoSdkServerConfigSchema: z.ZodMiniObject<{
|
|
4
|
+
providerAddress: z.ZodMiniString<string>;
|
|
5
|
+
providerPrivateKey: z.ZodMiniString<string>;
|
|
6
|
+
ssoBaseUrl: z.ZodMiniURL;
|
|
7
|
+
}, z.core.$strip>;
|
|
8
|
+
export type AlienSsoSdkServerConfig = z.infer<typeof AlienSsoSdkServerConfigSchema>;
|
|
9
|
+
export declare class AlienSsoSdkServer {
|
|
10
|
+
readonly config: AlienSsoSdkServerConfig;
|
|
11
|
+
readonly ssoBaseUrl: string;
|
|
12
|
+
constructor(config: AlienSsoSdkServerConfig);
|
|
13
|
+
authorize(codeChallenge: string): Promise<AuthorizeResponse | null>;
|
|
14
|
+
}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { AuthorizeResponseSchema, AuthorizeRequestSchema, } from './schema';
|
|
2
|
+
import { z } from 'zod/v4-mini';
|
|
3
|
+
import { signAsync } from '@noble/ed25519';
|
|
4
|
+
import { AuthenticationError, ValidationError } from './errors';
|
|
5
|
+
const DEFAULT_SSO_BASE_URL = 'https://sso.alien-api.com';
|
|
6
|
+
export const AlienSsoSdkServerConfigSchema = z.object({
|
|
7
|
+
providerAddress: z.string(),
|
|
8
|
+
providerPrivateKey: z.string(),
|
|
9
|
+
ssoBaseUrl: z.url(),
|
|
10
|
+
});
|
|
11
|
+
export class AlienSsoSdkServer {
|
|
12
|
+
constructor(config) {
|
|
13
|
+
const parsedConfig = AlienSsoSdkServerConfigSchema.parse(config);
|
|
14
|
+
this.config = parsedConfig;
|
|
15
|
+
this.ssoBaseUrl = parsedConfig.ssoBaseUrl || DEFAULT_SSO_BASE_URL;
|
|
16
|
+
}
|
|
17
|
+
async authorize(codeChallenge) {
|
|
18
|
+
if (!codeChallenge || codeChallenge.length !== 64) {
|
|
19
|
+
throw new ValidationError('Invalid code challenge');
|
|
20
|
+
}
|
|
21
|
+
// Note: order of fields important!
|
|
22
|
+
const signaturePayload = {
|
|
23
|
+
provider_address: this.config.providerAddress,
|
|
24
|
+
code_challenge: codeChallenge,
|
|
25
|
+
code_challenge_method: 'S256',
|
|
26
|
+
};
|
|
27
|
+
const message = JSON.stringify(signaturePayload);
|
|
28
|
+
const messageBytes = new TextEncoder().encode(message);
|
|
29
|
+
const privateKeyBytes = Buffer.from(this.config.providerPrivateKey, 'hex');
|
|
30
|
+
const signature = await signAsync(messageBytes, privateKeyBytes);
|
|
31
|
+
const authorizePayload = {
|
|
32
|
+
...signaturePayload,
|
|
33
|
+
provider_signature: Buffer.from(signature).toString('hex'),
|
|
34
|
+
};
|
|
35
|
+
AuthorizeRequestSchema.parse(authorizePayload);
|
|
36
|
+
const authorizationUrl = `${this.config.ssoBaseUrl}/authorize`;
|
|
37
|
+
const response = await fetch(authorizationUrl, {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
headers: {
|
|
40
|
+
'Content-Type': 'application/json',
|
|
41
|
+
},
|
|
42
|
+
body: JSON.stringify(authorizePayload),
|
|
43
|
+
});
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
const text = await response.text();
|
|
46
|
+
throw new AuthenticationError(`SSO Router Authorization failed: ${text}`);
|
|
47
|
+
}
|
|
48
|
+
const json = await response.json();
|
|
49
|
+
const { deep_link, polling_code, expired_at } = AuthorizeResponseSchema.parse(json);
|
|
50
|
+
const deepLinkBytes = Buffer.from(deep_link, 'utf8');
|
|
51
|
+
const deepLinkSignature = await signAsync(deepLinkBytes, privateKeyBytes);
|
|
52
|
+
const deepLinkUrl = new URL(deep_link);
|
|
53
|
+
deepLinkUrl.searchParams.set('link_signature', Buffer.from(deepLinkSignature).toString('hex'));
|
|
54
|
+
return {
|
|
55
|
+
deep_link: deepLinkUrl.toString(),
|
|
56
|
+
polling_code,
|
|
57
|
+
expired_at,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@alien_org/sso-sdk-core",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"exports": {
|
|
5
|
+
"./client": {
|
|
6
|
+
"import": "./dist/client/index.js",
|
|
7
|
+
"types": "./dist/types/client.d.ts"
|
|
8
|
+
},
|
|
9
|
+
"./server": {
|
|
10
|
+
"require": "./dist/server/index.js",
|
|
11
|
+
"types": "./dist/types/server.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/alien-id/sso-sdk-js.git"
|
|
17
|
+
},
|
|
18
|
+
"files": ["dist", "README.md"],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc",
|
|
21
|
+
"test": "jest",
|
|
22
|
+
"test:unit": "jest tests/unit",
|
|
23
|
+
"test:integration": "jest tests/integration",
|
|
24
|
+
"test:watch": "jest --watch",
|
|
25
|
+
"release": "npm publish --access public",
|
|
26
|
+
"prepublishOnly": "npm run build"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/crypto-js": "^4.2.2",
|
|
30
|
+
"@types/express": "^5.0.3",
|
|
31
|
+
"@types/jest": "^30.0.0",
|
|
32
|
+
"@types/node": "^24.3.0",
|
|
33
|
+
"@typescript-eslint/eslint-plugin": "^8.41.0",
|
|
34
|
+
"@typescript-eslint/parser": "^8.41.0",
|
|
35
|
+
"cross-fetch": "^4.1.0",
|
|
36
|
+
"eslint": "^9.34.0",
|
|
37
|
+
"eslint-config-prettier": "^10.1.8",
|
|
38
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
39
|
+
"express": "^5.1.0",
|
|
40
|
+
"jest": "^30.1.2",
|
|
41
|
+
"jest-environment-jsdom": "^30.1.2",
|
|
42
|
+
"nock": "^14.0.10",
|
|
43
|
+
"prettier": "^3.6.2",
|
|
44
|
+
"ts-jest": "^29.4.1",
|
|
45
|
+
"turbo": "^2.5.6"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@noble/ed25519": "^3.0.0",
|
|
49
|
+
"base64url": "^3.0.1",
|
|
50
|
+
"crypto-js": "^4.2.0",
|
|
51
|
+
"zod": "^4.1.5"
|
|
52
|
+
}
|
|
53
|
+
}
|